+// +----------------------------------------------------------------------
+namespace Common\Util;
+/**
+ * 列表树生成工具类
+ * 该类里的方法一部分来自OneThink,一部分来自网络,最后加上作者的修改形成完善的Tree类
+ * @author jry <598821125@qq.com>
+ */
+class Tree {
+ /**
+ * 用于树型数组完成递归格式的全局变量
+ * @author jry <598821125@qq.com>
+ */
+ private $formatTree;
+
+ /**
+ * 将格式数组转换为基于标题的树(实际还是列表,只是通过在相应字段加前缀实现类似树状结构)
+ * @param array $list
+ * @param integer $level 进行递归时传递用的参数
+ */
+ private function _toFormatTree($list, $level = 0, $title = 'title') {
+ foreach ($list as $key=>$val) {
+ $title_prefix = str_repeat(" ", $level*4);
+ $title_prefix .= "┝ ";
+ $val['level'] = $level;
+ $val['title_prefix'] = $level == 0 ? '' : $title_prefix;
+ $val['title_show'] = $level == 0 ? $val[$title] : $title_prefix.$val[$title];
+ if (!array_key_exists('_child', $val)) {
+ array_push($this->formatTree, $val);
+ } else {
+ $child = $val['_child'];
+ unset($val['_child']);
+ array_push($this->formatTree, $val);
+ $this->_toFormatTree($child, $level+1, $title); //进行下一层递归
+ }
+ }
+ return;
+ }
+
+ /**
+ * 将格式数组转换为树
+ * @param array $list
+ * @param integer $level 进行递归时传递用的参数
+ */
+ public function toFormatTree($list, $title = 'title', $pk='id', $pid = 'pid', $root = 0, $strict = true) {
+ $list = $this->list_to_tree($list, $pk, $pid, '_child', $root, $strict);
+ $this->formatTree = array();
+ $this->_toFormatTree($list, 0, $title);
+ return $this->formatTree;
+ }
+
+ /**
+ * 将数据集转换成Tree(真正的Tree结构)
+ * @param array $list 要转换的数据集
+ * @param string $pk ID标记字段
+ * @param string $pid parent标记字段
+ * @param string $child 子代key名称
+ * @param string $root 返回的根节点ID
+ * @param string $strict 默认严格模式
+ * @return array
+ */
+ public function list_to_tree($list, $pk='id', $pid = 'pid', $child = '_child', $root = 0, $strict = true) {
+ // 创建Tree
+ $tree = array();
+ if (is_array($list)) {
+ // 创建基于主键的数组引用
+ $refer = array();
+ foreach ($list as $key => $data) {
+ $refer[$data[$pk]] =& $list[$key];
+ }
+ foreach ($list as $key => $data) {
+ // 判断是否存在parent
+ $parent_id = $data[$pid];
+ if ($parent_id === null || (String)$root === $parent_id) {
+ $tree[] =& $list[$key];
+ } else {
+ if(isset($refer[$parent_id])){
+ $parent =& $refer[$parent_id];
+ $parent[$child][] =& $list[$key];
+ } else {
+ if($strict === false){
+ $tree[] =& $list[$key];
+ }
+ }
+ }
+ }
+ }
+ return $tree;
+ }
+
+ /**
+ * 将list_to_tree的树还原成列表
+ * @param array $tree 原来的树
+ * @param string $child 孩子节点的键
+ * @param string $order 排序显示的键,一般是主键 升序排列
+ * @param array $list 过渡用的中间数组,
+ * @return array 返回排过序的列表数组
+ */
+ public function tree_to_list($tree, $child = '_child', $order = 'id', &$list = array()) {
+ if (is_array($tree)) {
+ foreach ($tree as $key => $value) {
+ $reffer = $value;
+ if (isset($reffer[$child])) {
+ unset($reffer[$child]);
+ $this->tree_to_list($value[$child], $child, $order, $list);
+ }
+ $list[] = $reffer;
+ }
+ $list = $this->list_sort_by($list, $order, $sortby='asc');
+ }
+ return $list;
+ }
+
+ /**
+ * 对查询结果集进行排序
+ * @access public
+ * @param array $list 查询结果
+ * @param string $field 排序的字段名
+ * @param array $sortby 排序类型 asc正向排序 desc逆向排序 nat自然排序
+ * @return array
+ */
+ public function list_sort_by($list, $field, $sortby = 'asc') {
+ if (is_array($list)) {
+ $refer = $resultSet = array();
+ foreach ($list as $i => $data) {
+ $refer[$i] = &$data[$field];
+ }
+ switch ($sortby) {
+ case 'asc': // 正向排序
+ asort($refer);
+ break;
+ case 'desc':// 逆向排序
+ arsort($refer);
+ break;
+ case 'nat': // 自然排序
+ natcasesort($refer);
+ break;
+ }
+ foreach ($refer as $key=> $val) {
+ $resultSet[] = &$list[$key];
+ }
+ return $resultSet;
+ }
+ return false;
+ }
+
+ /**
+ * 在数据列表中搜索
+ * @access public
+ * @param array $list 数据列表
+ * @param mixed $condition 查询条件
+ * 支持 array('name'=>$value) 或者 name=$value
+ * @return array
+ */
+ function list_search($list, $condition) {
+ if(is_string($condition)) {
+ parse_str($condition, $condition);
+ }
+ // 返回的结果集合
+ $resultSet = array();
+ foreach ($list as $key => $data) {
+ $find = false;
+ foreach ($condition as $field=>$value){
+ if (isset($data[$field])) {
+ if (0 === strpos($value,'/')) {
+ $find = preg_match($value,$data[$field]);
+ } else if ($data[$field]==$value) {
+ $find = true;
+ }
+ }
+ }
+ if ($find) {
+ $resultSet[] = &$list[$key];
+ }
+ }
+ return $resultSet;
+ }
+}
diff --git a/Application/Common/Util/Zip.class.php b/Application/Common/Util/Zip.class.php
new file mode 100644
index 00000000..c72b6432
--- /dev/null
+++ b/Application/Common/Util/Zip.class.php
@@ -0,0 +1,525 @@
+visitFile(文件夹路径);
+ // print "当前文件夹的文件:\r\n";
+ // foreach($filelist as $file)
+ // printf("%s \r\n", $file);
+ // ------------------------------------------------------ //
+ var $fileList = array();
+ public function visitFile($path)
+ {
+ global $fileList;
+ $path = str_replace("\\", "/", $path);
+ $fdir = dir($path);
+
+ while(($file = $fdir->read()) !== false)
+ {
+ if($file == '.' || $file == '..'){ continue; }
+
+ $pathSub = preg_replace("*/{2,}*", "/", $path."/".$file); // 替换多个反斜杠
+ $fileList[] = is_dir($pathSub) ? $pathSub."/" : $pathSub;
+ if(is_dir($pathSub)){ $this->visitFile($pathSub); }
+ }
+ $fdir->close();
+ return $fileList;
+ }
+
+ private function unix2DosTime($unixtime = 0)
+ {
+ $timearray = ($unixtime == 0) ? getdate() : getdate($unixtime);
+
+ if($timearray['year'] < 1980)
+ {
+ $timearray['year'] = 1980;
+ $timearray['mon'] = 1;
+ $timearray['mday'] = 1;
+ $timearray['hours'] = 0;
+ $timearray['minutes'] = 0;
+ $timearray['seconds'] = 0;
+ }
+
+ return ( ($timearray['year'] - 1980) << 25)
+ | ($timearray['mon'] << 21)
+ | ($timearray['mday'] << 16)
+ | ($timearray['hours'] << 11)
+ | ($timearray['minutes'] << 5)
+ | ($timearray['seconds'] >> 1);
+ }
+
+ var $old_offset = 0;
+ private function addFile($data, $filename, $time = 0)
+ {
+ $filename = str_replace('\\', '/', $filename);
+
+ $dtime = dechex($this->unix2DosTime($time));
+ $hexdtime = '\x' . $dtime[6] . $dtime[7]
+ . '\x' . $dtime[4] . $dtime[5]
+ . '\x' . $dtime[2] . $dtime[3]
+ . '\x' . $dtime[0] . $dtime[1];
+ eval('$hexdtime = "' . $hexdtime . '";');
+
+ $fr = "\x50\x4b\x03\x04";
+ $fr .= "\x14\x00";
+ $fr .= "\x00\x00";
+ $fr .= "\x08\x00";
+ $fr .= $hexdtime;
+ $unc_len = strlen($data);
+ $crc = crc32($data);
+ $zdata = gzcompress($data);
+ $c_len = strlen($zdata);
+ $zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2);
+ $fr .= pack('V', $crc);
+ $fr .= pack('V', $c_len);
+ $fr .= pack('V', $unc_len);
+ $fr .= pack('v', strlen($filename));
+ $fr .= pack('v', 0);
+ $fr .= $filename;
+
+ $fr .= $zdata;
+
+ $fr .= pack('V', $crc);
+ $fr .= pack('V', $c_len);
+ $fr .= pack('V', $unc_len);
+
+ $this->datasec[] = $fr;
+ $new_offset = strlen(implode('', $this->datasec));
+
+ $cdrec = "\x50\x4b\x01\x02";
+ $cdrec .= "\x00\x00";
+ $cdrec .= "\x14\x00";
+ $cdrec .= "\x00\x00";
+ $cdrec .= "\x08\x00";
+ $cdrec .= $hexdtime;
+ $cdrec .= pack('V', $crc);
+ $cdrec .= pack('V', $c_len);
+ $cdrec .= pack('V', $unc_len);
+ $cdrec .= pack('v', strlen($filename) );
+ $cdrec .= pack('v', 0 );
+ $cdrec .= pack('v', 0 );
+ $cdrec .= pack('v', 0 );
+ $cdrec .= pack('v', 0 );
+ $cdrec .= pack('V', 32 );
+
+ $cdrec .= pack('V', $this->old_offset );
+ $this->old_offset = $new_offset;
+
+ $cdrec .= $filename;
+ $this->ctrl_dir[] = $cdrec;
+ }
+
+ var $eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00";
+ private function file()
+ {
+ $data = implode('', $this->datasec);
+ $ctrldir = implode('', $this->ctrl_dir);
+
+ return $data
+ . $ctrldir
+ . $this->eof_ctrl_dir
+ . pack('v', sizeof($this->ctrl_dir))
+ . pack('v', sizeof($this->ctrl_dir))
+ . pack('V', strlen($ctrldir))
+ . pack('V', strlen($data))
+ . "\x00\x00";
+ }
+
+ // ------------------------------------------------------ //
+ // #压缩到服务器
+ //
+ // $archive = new PHPZip();
+ // $archive->Zip("需压缩的文件所在目录", "ZIP压缩文件名");
+ // ------------------------------------------------------ //
+ public function Zip($dir, $saveName)
+ {
+ if(@!function_exists('gzcompress')){ return; }
+
+ ob_end_clean();
+ $filelist = $this->visitFile($dir);
+ if(count($filelist) == 0){ return; }
+
+ foreach($filelist as $file)
+ {
+ if(!file_exists($file) || !is_file($file)){ continue; }
+
+ $fd = fopen($file, "rb");
+ $content = @fread($fd, filesize($file));
+ fclose($fd);
+
+ // 1.删除$dir的字符(./folder/file.txt删除./folder/)
+ // 2.如果存在/就删除(/file.txt删除/)
+ $file = substr($file, strlen($dir));
+ if(substr($file, 0, 1) == "\\" || substr($file, 0, 1) == "/"){ $file = substr($file, 1); }
+
+ $this->addFile($content, $file);
+ }
+ $out = $this->file();
+
+ $fp = fopen($saveName, "wb");
+ fwrite($fp, $out, strlen($out));
+ fclose($fp);
+ }
+
+ // ------------------------------------------------------ //
+ // #压缩并直接下载
+ //
+ // $archive = new PHPZip();
+ // $archive->ZipAndDownload("需压缩的文件所在目录");
+ // ------------------------------------------------------ //
+ public function ZipAndDownload($dir)
+ {
+ if(@!function_exists('gzcompress')){ return; }
+
+ ob_end_clean();
+ $filelist = $this->visitFile($dir);
+ if(count($filelist) == 0){ return; }
+
+ foreach($filelist as $file)
+ {
+ if(!file_exists($file) || !is_file($file)){ continue; }
+
+ $fd = fopen($file, "rb");
+ $content = @fread($fd, filesize($file));
+ fclose($fd);
+
+ // 1.删除$dir的字符(./folder/file.txt删除./folder/)
+ // 2.如果存在/就删除(/file.txt删除/)
+ $file = substr($file, strlen($dir));
+ if(substr($file, 0, 1) == "\\" || substr($file, 0, 1) == "/"){ $file = substr($file, 1); }
+
+ $this->addFile($content, $file);
+ }
+ $out = $this->file();
+
+ @header('Content-Encoding: none');
+ @header('Content-Type: application/zip');
+ @header('Content-Disposition: attachment ; filename=Farticle'.date("YmdHis", time()).'.zip');
+ @header('Pragma: no-cache');
+ @header('Expires: 0');
+ print($out);
+ }
+
+
+ /**********************************************************
+ * 解压部分
+ **********************************************************/
+ // ------------------------------------------------------ //
+ // ReadCentralDir($zip, $zipfile)
+ // $zip是经过@fopen($zipfile, 'rb')打开的
+ // $zipfile是zip文件的路径
+ // ------------------------------------------------------ //
+ private function ReadCentralDir($zip, $zipfile)
+ {
+ $size = filesize($zipfile);
+ $max_size = ($size < 277) ? $size : 277;
+
+ @fseek($zip, $size - $max_size);
+ $pos = ftell($zip);
+ $bytes = 0x00000000;
+
+ while($pos < $size)
+ {
+ $byte = @fread($zip, 1);
+ $bytes = ($bytes << 8) | Ord($byte);
+ $pos++;
+ if($bytes == 0x504b0506){ break; }
+ }
+
+ $data = unpack('vdisk/vdisk_start/vdisk_entries/ventries/Vsize/Voffset/vcomment_size', fread($zip, 18));
+
+ $centd['comment'] = ($data['comment_size'] != 0) ? fread($zip, $data['comment_size']) : ''; // 注释
+ $centd['entries'] = $data['entries'];
+ $centd['disk_entries'] = $data['disk_entries'];
+ $centd['offset'] = $data['offset'];
+ $centd['disk_start'] = $data['disk_start'];
+ $centd['size'] = $data['size'];
+ $centd['disk'] = $data['disk'];
+ return $centd;
+ }
+
+ private function ReadCentralFileHeaders($zip)
+ {
+ $binary_data = fread($zip, 46);
+ $header = unpack('vchkid/vid/vversion/vversion_extracted/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len/vcomment_len/vdisk/vinternal/Vexternal/Voffset', $binary_data);
+
+ $header['filename'] = ($header['filename_len'] != 0) ? fread($zip, $header['filename_len']) : '';
+ $header['extra'] = ($header['extra_len'] != 0) ? fread($zip, $header['extra_len']) : '';
+ $header['comment'] = ($header['comment_len'] != 0) ? fread($zip, $header['comment_len']) : '';
+
+
+ if($header['mdate'] && $header['mtime'])
+ {
+ $hour = ($header['mtime'] & 0xF800) >> 11;
+ $minute = ($header['mtime'] & 0x07E0) >> 5;
+ $seconde = ($header['mtime'] & 0x001F) * 2;
+ $year = (($header['mdate'] & 0xFE00) >> 9) + 1980;
+ $month = ($header['mdate'] & 0x01E0) >> 5;
+ $day = $header['mdate'] & 0x001F;
+ $header['mtime'] = mktime($hour, $minute, $seconde, $month, $day, $year);
+ } else {
+ $header['mtime'] = time();
+ }
+ $header['stored_filename'] = $header['filename'];
+ $header['status'] = 'ok';
+ if(substr($header['filename'], -1) == '/'){ $header['external'] = 0x41FF0010; } // 判断是否文件夹
+ return $header;
+ }
+
+ private function ReadFileHeader($zip)
+ {
+ $binary_data = fread($zip, 30);
+ $data = unpack('vchk/vid/vversion/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len', $binary_data);
+
+ $header['filename'] = fread($zip, $data['filename_len']);
+ $header['extra'] = ($data['extra_len'] != 0) ? fread($zip, $data['extra_len']) : '';
+ $header['compression'] = $data['compression'];
+ $header['size'] = $data['size'];
+ $header['compressed_size'] = $data['compressed_size'];
+ $header['crc'] = $data['crc'];
+ $header['flag'] = $data['flag'];
+ $header['mdate'] = $data['mdate'];
+ $header['mtime'] = $data['mtime'];
+
+ if($header['mdate'] && $header['mtime']){
+ $hour = ($header['mtime'] & 0xF800) >> 11;
+ $minute = ($header['mtime'] & 0x07E0) >> 5;
+ $seconde = ($header['mtime'] & 0x001F) * 2;
+ $year = (($header['mdate'] & 0xFE00) >> 9) + 1980;
+ $month = ($header['mdate'] & 0x01E0) >> 5;
+ $day = $header['mdate'] & 0x001F;
+ $header['mtime'] = mktime($hour, $minute, $seconde, $month, $day, $year);
+ }else{
+ $header['mtime'] = time();
+ }
+
+ $header['stored_filename'] = $header['filename'];
+ $header['status'] = "ok";
+ return $header;
+ }
+
+ private function ExtractFile($header, $to, $zip)
+ {
+ $header = $this->readfileheader($zip);
+
+ if(substr($to, -1) != "/"){ $to .= "/"; }
+ if(!@is_dir($to)){ @mkdir($to, 0777); }
+
+ $pth = explode("/", dirname($header['filename']));
+ for($i=0; isset($pth[$i]); $i++){
+ if(!$pth[$i]){ continue; }
+ $pthss .= $pth[$i]."/";
+ if(!is_dir($to.$pthss)){ @mkdir($to.$pthss, 0777); }
+ }
+
+ if(!($header['external'] == 0x41FF0010) && !($header['external'] == 16))
+ {
+ if($header['compression'] == 0)
+ {
+ $fp = @fopen($to.$header['filename'], 'wb');
+ if(!$fp){ return(-1); }
+ $size = $header['compressed_size'];
+
+ while($size != 0)
+ {
+ $read_size = ($size < 2048 ? $size : 2048);
+ $buffer = fread($zip, $read_size);
+ $binary_data = pack('a'.$read_size, $buffer);
+ @fwrite($fp, $binary_data, $read_size);
+ $size -= $read_size;
+ }
+ fclose($fp);
+ touch($to.$header['filename'], $header['mtime']);
+
+ }else{
+
+ $fp = @fopen($to.$header['filename'].'.gz', 'wb');
+ if(!$fp){ return(-1); }
+ $binary_data = pack('va1a1Va1a1', 0x8b1f, Chr($header['compression']), Chr(0x00), time(), Chr(0x00), Chr(3));
+
+ fwrite($fp, $binary_data, 10);
+ $size = $header['compressed_size'];
+
+ while($size != 0)
+ {
+ $read_size = ($size < 1024 ? $size : 1024);
+ $buffer = fread($zip, $read_size);
+ $binary_data = pack('a'.$read_size, $buffer);
+ @fwrite($fp, $binary_data, $read_size);
+ $size -= $read_size;
+ }
+
+ $binary_data = pack('VV', $header['crc'], $header['size']);
+ fwrite($fp, $binary_data, 8);
+ fclose($fp);
+
+ $gzp = @gzopen($to.$header['filename'].'.gz', 'rb') or die("Cette archive est compress!");
+
+ if(!$gzp){ return(-2); }
+ $fp = @fopen($to.$header['filename'], 'wb');
+ if(!$fp){ return(-1); }
+ $size = $header['size'];
+
+ while($size != 0)
+ {
+ $read_size = ($size < 2048 ? $size : 2048);
+ $buffer = gzread($gzp, $read_size);
+ $binary_data = pack('a'.$read_size, $buffer);
+ @fwrite($fp, $binary_data, $read_size);
+ $size -= $read_size;
+ }
+ fclose($fp); gzclose($gzp);
+
+ touch($to.$header['filename'], $header['mtime']);
+ @unlink($to.$header['filename'].'.gz');
+ }
+ }
+ return true;
+ }
+
+ // ------------------------------------------------------ //
+ // #解压文件
+ //
+ // $archive = new PHPZip();
+ // $zipfile = "ZIP压缩文件名";
+ // $savepath = "解压缩目录名";
+ // $zipfile = $unzipfile;
+ // $savepath = $unziptarget;
+ // $array = $archive->GetZipInnerFilesInfo($zipfile);
+ // $filecount = 0;
+ // $dircount = 0;
+ // $failfiles = array();
+ // set_time_limit(0); // 修改为不限制超时时间(默认为30秒)
+ //
+ // for($i=0; $iunZip($zipfile, $savepath, $i) > 0){
+ // $filecount++;
+ // }else{
+ // $failfiles[] = $array[$i][filename];
+ // }
+ // }else{
+ // $dircount++;
+ // }
+ // }
+ // set_time_limit(30);
+ //printf("文件夹:%d 解压文件:%d 失败:%d \r\n", $dircount, $filecount, count($failfiles));
+ //if(count($failfiles) > 0){
+ // foreach($failfiles as $file){
+ // printf("·%s \r\n", $file);
+ // }
+ //}
+ // ------------------------------------------------------ //
+ public function unZip($zipfile, $to, $index = Array(-1))
+ {
+ $ok = 0;
+ $zip = @fopen($zipfile, 'rb');
+ if(!$zip){ return(-1); }
+
+ $cdir = $this->ReadCentralDir($zip, $zipfile);
+ $pos_entry = $cdir['offset'];
+
+ if(!is_array($index)){ $index = array($index); }
+ for($i=0; $index[$i]; $i++)
+ {
+ if(intval($index[$i]) != $index[$i] || $index[$i] > $cdir['entries'])
+ {
+ return(-1);
+ }
+ }
+
+ for($i=0; $i<$cdir['entries']; $i++)
+ {
+ @fseek($zip, $pos_entry);
+ $header = $this->ReadCentralFileHeaders($zip);
+ $header['index'] = $i;
+ $pos_entry = ftell($zip);
+ @rewind($zip);
+ fseek($zip, $header['offset']);
+ if(in_array("-1", $index) || in_array($i, $index))
+ {
+ $stat[$header['filename']] = $this->ExtractFile($header, $to, $zip);
+ }
+ }
+
+ fclose($zip);
+ return $stat;
+ }
+
+
+ /**********************************************************
+ * 其它部分
+ **********************************************************/
+ // ------------------------------------------------------ //
+ // #获取被压缩文件的信息
+ //
+ // $archive = new PHPZip();
+ // $array = $archive->GetZipInnerFilesInfo(ZIP压缩文件名);
+ // for($i=0; $i·%s \r\n", $array[$i][filename]);
+ // foreach($array[$i] as $key => $value)
+ // printf("%s => %s \r\n", $key, $value);
+ // print "\r\n------------------------------------
\r\n\r\n";
+ // }
+ // ------------------------------------------------------ //
+ public function GetZipInnerFilesInfo($zipfile)
+ {
+ $zip = @fopen($zipfile, 'rb');
+ if(!$zip){ return(0); }
+ $centd = $this->ReadCentralDir($zip, $zipfile);
+
+ @rewind($zip);
+ @fseek($zip, $centd['offset']);
+ $ret = array();
+
+ for($i=0; $i<$centd['entries']; $i++)
+ {
+ $header = $this->ReadCentralFileHeaders($zip);
+ $header['index'] = $i;
+ $info = array(
+ 'filename' => $header['filename'], // 文件名
+ 'stored_filename' => $header['stored_filename'], // 压缩后文件名
+ 'size' => $header['size'], // 大小
+ 'compressed_size' => $header['compressed_size'], // 压缩后大小
+ 'crc' => strtoupper(dechex($header['crc'])), // CRC32
+ 'mtime' => date("Y-m-d H:i:s",$header['mtime']), // 文件修改时间
+ 'comment' => $header['comment'], // 注释
+ 'folder' => ($header['external'] == 0x41FF0010 || $header['external'] == 16) ? 1 : 0, // 是否为文件夹
+ 'index' => $header['index'], // 文件索引
+ 'status' => $header['status'] // 状态
+ );
+ $ret[] = $info;
+ unset($header);
+ }
+ fclose($zip);
+ return $ret;
+ }
+
+ // ------------------------------------------------------ //
+ // #获取压缩文件的注释
+ //
+ // $archive = new PHPZip();
+ // echo $archive->GetZipComment(ZIP压缩文件名);
+ // ------------------------------------------------------ //
+ public function GetZipComment($zipfile)
+ {
+ $zip = @fopen($zipfile, 'rb');
+ if(!$zip){ return(0); }
+ $centd = $this->ReadCentralDir($zip, $zipfile);
+ fclose($zip);
+ return $centd[comment];
+ }
+}
diff --git a/Application/Home/Conf/config.php b/Application/Home/Conf/config.php
new file mode 100644
index 00000000..85ca3531
--- /dev/null
+++ b/Application/Home/Conf/config.php
@@ -0,0 +1,19 @@
+
+// +----------------------------------------------------------------------
+return array(
+ // 路由配置
+ 'URL_ROUTER_ON' => true,
+ 'URL_MAP_RULES' => array(
+ ),
+ 'URL_ROUTE_RULES' => array(
+ 'page/:id\d' => 'nav/page',
+ 'lists/:cid\d' => 'nav/lists',
+ 'post/:id\d' => 'nav/post',
+ ),
+);
diff --git a/Application/Home/Controller/AddonController.class.php b/Application/Home/Controller/AddonController.class.php
new file mode 100644
index 00000000..f1142c49
--- /dev/null
+++ b/Application/Home/Controller/AddonController.class.php
@@ -0,0 +1,48 @@
+
+// +----------------------------------------------------------------------
+namespace Home\Controller;
+/**
+ * 扩展控制器
+ * 该类参考了OneThink的部分实现
+ * 用于调度各个扩展的URL访问需求
+ */
+class AddonController extends HomeController {
+ /**
+ * 外部执行插件方法
+ * @author jry <598821125@qq.com>
+ */
+ public function execute($_addons = null, $_controller = null, $_action = null) {
+ if (C('URL_CASE_INSENSITIVE')) {
+ $_addons = ucfirst(parse_name($_addons, 1));
+ $_controller = parse_name($_controller, 1);
+ }
+
+ $TMPL_PARSE_STRING = C('TMPL_PARSE_STRING');
+ $TMPL_PARSE_STRING['__ADDONROOT__'] = __ROOT__ . "/Addons/{$_addons}";
+ C('TMPL_PARSE_STRING', $TMPL_PARSE_STRING);
+
+ if (!empty($_addons) && !empty($_controller) && !empty($_action)) {
+ $Addons = A("Addons://{$_addons}/{$_controller}")->$_action();
+ } else {
+ $this->error('没有指定插件名称,控制器或操作!');
+ }
+ }
+
+ /**
+ * 模板显示 调用内置的模板引擎显示方法,
+ * @access protected
+ * @param string $templateFile 指定要调用的模板文件
+ * @return void
+ */
+ protected function display($template) {
+ $file = T('Addons://' . parse_name($_GET['_addons'], 1) . '@./' . ucfirst($_GET['_controller']) . '/' . $_GET['_action']);
+ define('IS_ADDON', true);
+ parent::display($file); // 重要:要避免陷入$this->display()循环
+ }
+}
diff --git a/Application/Home/Controller/AdminController.class.php b/Application/Home/Controller/AdminController.class.php
new file mode 100644
index 00000000..969b058d
--- /dev/null
+++ b/Application/Home/Controller/AdminController.class.php
@@ -0,0 +1,23 @@
+
+// +----------------------------------------------------------------------
+namespace Home\Controller;
+use Think\Controller;
+/**
+ * 跳转到后台控制器
+ * @author jry <598821125@qq.com>
+ */
+class AdminController extends Controller {
+ /**
+ * 自动跳转到后台入口文件
+ * @author jry <598821125@qq.com>
+ */
+ public function index() {
+ redirect(C('HOME_PAGE').'/admin.php');
+ }
+}
diff --git a/Application/Home/Controller/HomeController.class.php b/Application/Home/Controller/HomeController.class.php
new file mode 100644
index 00000000..db8e7289
--- /dev/null
+++ b/Application/Home/Controller/HomeController.class.php
@@ -0,0 +1,45 @@
+
+// +----------------------------------------------------------------------
+namespace Home\Controller;
+use Common\Controller\ControllerController;
+/**
+ * 前台公共控制器
+ * 为防止多分组Controller名称冲突,公共Controller名称统一使用模块名
+ * @author jry <598821125@qq.com>
+ */
+class HomeController extends ControllerController {
+ /**
+ * 用户信息
+ * @author jry <598821125@qq.com>
+ */
+ protected $user_info;
+
+ /**
+ * 初始化方法
+ * @author jry <598821125@qq.com>
+ */
+ protected function _initialize() {
+ // 系统开关
+ if (!C('TOGGLE_WEB_SITE')) {
+ $this->error('站点已经关闭,请稍后访问~');
+ }
+
+ // 监听行为扩展
+ try {
+ \Think\Hook::listen('corethink_behavior');
+ } catch(\Exception $e) {
+ file_put_contents(RUNTIME_PATH.'error.json', json_encode($e->getMessage()));
+ }
+
+ // 记录当前url
+ if (MODULE_NAME !== 'User' && IS_GET === true) {
+ cookie('forward', (is_ssl()?'https://':'http://').$_SERVER['HTTP_HOST'].$_SERVER["REQUEST_URI"]);
+ }
+ }
+}
diff --git a/Application/Home/Controller/IndexController.class.php b/Application/Home/Controller/IndexController.class.php
new file mode 100644
index 00000000..6dbc5573
--- /dev/null
+++ b/Application/Home/Controller/IndexController.class.php
@@ -0,0 +1,24 @@
+
+// +----------------------------------------------------------------------
+namespace Home\Controller;
+use Common\Util\Think\Page;
+/**
+ * 前台默认控制器
+ * @author jry <598821125@qq.com>
+ */
+class IndexController extends HomeController {
+ /**
+ * 默认方法
+ * @author jry <598821125@qq.com>
+ */
+ public function index() {
+ $this->assign('meta_title', "首页");
+ $this->display();
+ }
+}
diff --git a/Application/Home/Controller/NavController.class.php b/Application/Home/Controller/NavController.class.php
new file mode 100644
index 00000000..9e762538
--- /dev/null
+++ b/Application/Home/Controller/NavController.class.php
@@ -0,0 +1,122 @@
+
+// +----------------------------------------------------------------------
+namespace Home\Controller;
+use Common\Util\Think\Page;
+/**
+ * 导航控制器
+ * @author jry <598821125@qq.com>
+ */
+class NavController extends HomeController {
+ /**
+ * 默认方法
+ * @author jry <598821125@qq.com>
+ */
+ public function index() {
+ $this->assign('meta_title', "首页");
+ $this->display();
+ }
+
+ /**
+ * 单页类型
+ * @author jry <598821125@qq.com>
+ */
+ public function page($id) {
+ $nav_object = D('Admin/Nav');
+ $con['id'] = $id;
+ $con['status'] = 1;
+ $info = $nav_object->where($con)->find();
+
+ $this->assign('info', $info);
+ $this->assign('meta_title', $info['title']);
+ $this->display();
+ }
+
+ /**
+ * 文章列表
+ * @author jry <598821125@qq.com>
+ */
+ public function lists($cid) {
+ $nav_object = D('Admin/Nav');
+ $con['id'] = $cid;
+ $con['status'] = 1;
+ $info = $nav_object->where($con)->find();
+
+ // 文章列表
+ $map['status'] = 1;
+ $map['cid'] = $cid;
+ $p = $_GET["p"] ? : 1;
+ $post_object = D('Admin/Post');
+ $data_list = $post_object
+ ->where($map)
+ ->page($p, C("ADMIN_PAGE_ROWS"))
+ ->order("sort desc,id desc")
+ ->select();
+ $page = new Page(
+ $post_object->where($map)->count(),
+ C("ADMIN_PAGE_ROWS")
+ );
+
+ $this->assign('data_list', $data_list);
+ $this->assign('page', $page->show());
+ $this->assign('meta_title', $info['title']);
+ $this->display();
+ }
+
+ /**
+ * 文章详情
+ * @author jry <598821125@qq.com>
+ */
+ public function post($id) {
+ $post_object = D('Admin/Post');
+ $con['id'] = $id;
+ $con['status'] = 1;
+ $info = $post_object->where($con)->find();
+
+ // 阅读量加1
+ $result = $post_object->where(array('id' => $id))->SetInc('view_count');
+
+ $this->assign('info', $info);
+ $this->assign('meta_title', $info['title']);
+ $this->display('page');
+ }
+
+ /**
+ * 系统配置
+ * @author jry <598821125@qq.com>
+ */
+ public function config($name = '') {
+ $data_list = C($name);
+ $this->assign('data_list', $data_list);
+ $this->assign('meta_title', '系统配置');
+ $this->display();
+ }
+
+ /**
+ * 导航
+ * @author jry <598821125@qq.com>
+ */
+ public function nav($group = 'wap_bottom') {
+ $data_list = D('Admin/Nav')->getNavTree(0, $group);
+ $this->assign('data_list', $data_list);
+ $this->assign('meta_title', '导航列表');
+ $this->display();
+ }
+
+ /**
+ * 模块
+ * @author jry <598821125@qq.com>
+ */
+ public function module() {
+ $map['status'] = 1;
+ $data_list = D('Admin/MODULE')->where($map)->select();
+ $this->assign('data_list', $data_list);
+ $this->assign('meta_title', '模块列表');
+ $this->display();
+ }
+}
diff --git a/Application/Home/Controller/UploadController.class.php b/Application/Home/Controller/UploadController.class.php
new file mode 100644
index 00000000..1ecd72e3
--- /dev/null
+++ b/Application/Home/Controller/UploadController.class.php
@@ -0,0 +1,47 @@
+
+// +----------------------------------------------------------------------
+namespace Home\Controller;
+/**
+ * 上传控制器
+ * @author jry <598821125@qq.com>
+ */
+class UploadController extends HomeController {
+ /**
+ * 上传
+ * @author jry <598821125@qq.com>
+ */
+ public function upload() {
+ if (is_login() || (C('AUTH_KEY') === $_SERVER['HTTP_UPLOADTOKEN'])) {
+ $return = json_encode(D('Admin/Upload')->upload());
+ exit($return);
+ }
+ }
+
+ /**
+ * 下载
+ * @author jry <598821125@qq.com>
+ */
+ public function download($token) {
+ if (empty($token)) {
+ $this->error('token参数错误!');
+ }
+
+ //解密下载token
+ $file_md5 = \Think\Crypt::decrypt($token, user_md5(is_login()));
+ if (!$file_md5) {
+ $this->error('下载链接已过期,请刷新页面!');
+ }
+
+ $upload_object = D('Admin/Upload');
+ $file_id = $upload_object->getFieldByMd5($file_md5, 'id');
+ if (!$upload_object->download($file_id)) {
+ $this->error($upload_object->getError());
+ }
+ }
+}
diff --git a/Application/Home/TagLib/Lingyun.class.php b/Application/Home/TagLib/Lingyun.class.php
new file mode 100644
index 00000000..e4992ec2
--- /dev/null
+++ b/Application/Home/TagLib/Lingyun.class.php
@@ -0,0 +1,52 @@
+
+// +----------------------------------------------------------------------
+namespace Home\TagLib;
+use Think\Template\TagLib;
+/**
+ * 标签库
+ * @author jry <598821125@qq.com>
+ */
+class Lingyun extends TagLib {
+ /**
+ * 定义标签列表
+ * @author jry <598821125@qq.com>
+ */
+ protected $tags = array(
+ 'sql_query' => array('attr' => 'sql,result', 'close' => 0), //SQL查询
+ 'nav_list' => array('attr' => 'name,pid,group', 'close' => 1), //导航列表
+ );
+
+ /**
+ * SQL查询
+ */
+ public function _sql_query($tag, $content) {
+ $sql = $tag['sql'];
+ $result = !empty($tag['result']) ? $tag['result'] : 'result';
+ $parse = 'query("'.$sql.'");';
+ $parse .= 'if($'.$result.'):?>'.$content;
+ $parse .= "";
+ return $parse;
+ }
+
+ /**
+ * 导航列表
+ */
+ public function _nav_list($tag, $content) {
+ $name = $tag['name'];
+ $pid = $tag['pid'] ? : 0;
+ $group = $tag['group'] ? : 'main';
+ $parse = 'getNavTree('.$pid.', "'.$group.'");';
+ $parse .= ' ?>';
+ $parse .= '';
+ $parse .= $content;
+ $parse .= ' ';
+ return $parse;
+ }
+}
diff --git a/Application/Home/View/Index/index.html b/Application/Home/View/Index/index.html
new file mode 100644
index 00000000..19433561
--- /dev/null
+++ b/Application/Home/View/Index/index.html
@@ -0,0 +1,244 @@
+
+
+{:C('WEB_SITE_TITLE')}-{:C('WEB_SITE_SLOGAN')}
+
+
+
+
+
+
+
+
+
+
+ {:C('WEB_SITE_TITLE')}
+ {:C('CURRENT_VERSION')}
+
+
+
+ {:C('WEB_SITE_DESCRIPTION')}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
开发模块化
+
+
+
+ 系统功能全面模块化、插件化;可以用来对系统功能进行无限扩展。
+
+
+
+
+
+
+
+
+
Builder页面生成技术
+
+
+
+ 独创的Builder页面自动生成技术极大的解放了后端开发者的工作,只需要专注于业务逻辑,页面交给Builder帮您完成。
+
+
+
+
+
+
+
+
+
多终端多平台支持
+
+
+
+ 支持PC、手机Wap、微信等常见界面的页面响应式。
+
+
+
+
+
+
+
+
+
文档齐全开发简单
+
+
+
+ 官方开发文档齐全、更新及时,让二次开发变得简单。
+
+
+
+
+
+
+
+
+
基于ThinkPHP3.2.3最新版
+
+
+
+ ThinkPHP 是一个免费开源的,快速、简单的面向对象的 轻量级PHP开发框架 ,遵循Apache2开源协议发布,是为了敏捷WEB应用开发和简化企业应用开发而诞生的。
+
+
+
+
+
+
+
+
+
基于Bootstrap3.3.5
+
+
+
+ 官方开发cui前端框架,是在Bootstrap3.3.5基础上扩展出来的完全兼容Bootstrap3的最适合国人的前端框架。
+
+
+
+
+
+
+
+
+
独家两种后台模式
+
+
+
+ 独创的经典和多标签两种后台,一键切换,让您在合适的场景选择合适的模式。
+
+
+
+
+
+
+
+
+
自主产权技术支持完善
+
+
+
+ 本系统经国家教育部科技查新工作站查新认证、具有国家版权中心软件著作权,完全官方自主研发核心技术,技术支持完善。
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Application/Home/View/Nav/lists.html b/Application/Home/View/Nav/lists.html
new file mode 100644
index 00000000..6da904d2
--- /dev/null
+++ b/Application/Home/View/Nav/lists.html
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
在后台[系统-导航管理]可以管理 {$meta_title} 的文章
+
+
+
+
+
diff --git a/Application/Home/View/Nav/page.html b/Application/Home/View/Nav/page.html
new file mode 100644
index 00000000..d469afb4
--- /dev/null
+++ b/Application/Home/View/Nav/page.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+ {$info['content']|parse_content}
+
+
+
+
+
在后台[系统-导航管理]可以编辑 {$meta_title} 的内容
+
+
+
+
+
diff --git a/Application/Home/View/Public/css/home.css b/Application/Home/View/Public/css/home.css
new file mode 100644
index 00000000..2107e695
--- /dev/null
+++ b/Application/Home/View/Public/css/home.css
@@ -0,0 +1,143 @@
+/* 全局 */
+body {
+ font-family: 'Microsoft YaHei','Helvetica Neue',sans-serif,SimHei;
+ -webkit-font-smoothing: antialiased;
+ background: #fff;
+}
+
+
+/* 顶部导航 */
+.top-nav {
+ border-bottom: 1px solid #eee;;
+}
+.top-nav .dropdown-menu {
+ box-shadow: none;
+ -webkit-box-shadow: none;
+}
+.top-nav .navbar-nav>li>a {
+ font-size: 12px;
+ padding: 6px 8px 4px;
+}
+.top-nav .navbar-right>li>a:focus,
+.top-nav .navbar-right>li>a:hover{
+ color: #2699ed;
+ background: #fff;
+ border-left: 1px solid #eee;
+ border-right: 1px solid #eee;
+ padding: 6px 7px 4px;
+}
+.top-nav .navbar-right>li>a:hover .fa-angle-down {
+ transform: rotateZ(180deg);
+ -webkit-transform: rotateZ(180deg);
+ -moz-transform: rotateZ(180deg);
+ -o-transform: rotateZ(180deg);
+ -ms-transform: rotateZ(180deg);
+}
+.top-nav .dropdown:hover .dropdown-menu {
+ display: block;
+ border-top: 0;
+ border-color: #eee;
+}
+
+
+/* 主导航 */
+.main-nav {
+ background: #fff;
+ border-top: 0;
+}
+.main-nav .navbar-nav>li>a {
+ font-size: 15px;
+ -webkit-transition: all 0.2s;
+}
+.main-nav .navbar-nav li>a:focus,
+.main-nav .navbar-nav li>a:hover{
+ color: #2699ed;
+}
+.main-nav .navbar-nav>li.dropdown.open>a{
+ color: #2699ed !important;
+}
+@media (min-width: 768px) {
+ .main-nav .navbar-brand {
+ padding: 30px 15px;
+ font-size: 20px;
+ color: #fff;
+ }
+ .main-nav .navbar-brand img {
+ height: 50px;
+ margin-top: -10px;
+ }
+ .main-nav ul.nav>li>a {
+ padding: 34px 20px;
+ }
+ .main-nav .navbar-nav>.active>a,
+ .main-nav .navbar-nav>.active>a:hover {
+ background: none;
+ color: #777;
+ }
+ .main-nav .navbar-btn {
+ margin-top: 27px;
+ margin-bottom: 27px
+ }
+}
+@media (max-width: 768px) {
+ .main-nav .navbar-toggle {
+ border: 0;
+ }
+}
+
+
+/* 页面主体 */
+.main {
+ padding: 20px 0;
+ min-height: 100px;
+}
+
+
+/* 页面底部 */
+.footer {
+ border-top: 1px solid #eee;
+ background-color: #f5f5f5;
+ padding: 40px 0;
+ font-size: 13px;
+ color: #999;
+}
+.footer .footnav-list h4 {
+ text-decoration: none;
+ padding-bottom: 10px;
+ font-size: 16px;
+ color: #777;
+ cursor: pointer;
+}
+.footer .footnav-list ul li {
+ padding: 6px 0;
+}
+.footer .footnav-list ul li a {
+ color: #999;
+ text-decoration: none;
+ line-height: 1;
+}
+.footer .footnav-list ul li a:hover {
+ color: #2699ed;
+}
+.footer .qrcode-list {
+ padding-top: 40px;
+}
+.footer .qrcode-list div>p{
+ font-size: 13px;
+ color: #999;
+ padding: 6px 0;
+}
+
+/* 页面底部版权 */
+.footer-bottom {
+ background-color: #2699ed;
+ padding: 15px 0 5px;
+ color: #fff;
+}
+.footer-bottom ul li {
+ font-size: 13px;
+ font-weight: 100;
+}
+.footer-bottom a {
+ color: #fff;
+}
diff --git a/Application/Home/View/Public/img/default/android.png b/Application/Home/View/Public/img/default/android.png
new file mode 100644
index 00000000..c1dc6301
Binary files /dev/null and b/Application/Home/View/Public/img/default/android.png differ
diff --git a/Application/Home/View/Public/img/default/avatar.png b/Application/Home/View/Public/img/default/avatar.png
new file mode 100644
index 00000000..6f3ffbbf
Binary files /dev/null and b/Application/Home/View/Public/img/default/avatar.png differ
diff --git a/Application/Home/View/Public/img/default/default.gif b/Application/Home/View/Public/img/default/default.gif
new file mode 100644
index 00000000..50577f62
Binary files /dev/null and b/Application/Home/View/Public/img/default/default.gif differ
diff --git a/Application/Home/View/Public/img/default/ios.png b/Application/Home/View/Public/img/default/ios.png
new file mode 100644
index 00000000..29b9813b
Binary files /dev/null and b/Application/Home/View/Public/img/default/ios.png differ
diff --git a/Application/Home/View/Public/img/file/accdb.png b/Application/Home/View/Public/img/file/accdb.png
new file mode 100644
index 00000000..d848de6f
Binary files /dev/null and b/Application/Home/View/Public/img/file/accdb.png differ
diff --git a/Application/Home/View/Public/img/file/avi.png b/Application/Home/View/Public/img/file/avi.png
new file mode 100644
index 00000000..e5bdb617
Binary files /dev/null and b/Application/Home/View/Public/img/file/avi.png differ
diff --git a/Application/Home/View/Public/img/file/bmp.png b/Application/Home/View/Public/img/file/bmp.png
new file mode 100644
index 00000000..f8904ea5
Binary files /dev/null and b/Application/Home/View/Public/img/file/bmp.png differ
diff --git a/Application/Home/View/Public/img/file/css.png b/Application/Home/View/Public/img/file/css.png
new file mode 100644
index 00000000..096ffa0a
Binary files /dev/null and b/Application/Home/View/Public/img/file/css.png differ
diff --git a/Application/Home/View/Public/img/file/doc.png b/Application/Home/View/Public/img/file/doc.png
new file mode 100644
index 00000000..0d64e489
Binary files /dev/null and b/Application/Home/View/Public/img/file/doc.png differ
diff --git a/Application/Home/View/Public/img/file/docx.png b/Application/Home/View/Public/img/file/docx.png
new file mode 100644
index 00000000..dc9af8dc
Binary files /dev/null and b/Application/Home/View/Public/img/file/docx.png differ
diff --git a/Application/Home/View/Public/img/file/eml.png b/Application/Home/View/Public/img/file/eml.png
new file mode 100644
index 00000000..c683c808
Binary files /dev/null and b/Application/Home/View/Public/img/file/eml.png differ
diff --git a/Application/Home/View/Public/img/file/eps.png b/Application/Home/View/Public/img/file/eps.png
new file mode 100644
index 00000000..f8d4f2af
Binary files /dev/null and b/Application/Home/View/Public/img/file/eps.png differ
diff --git a/Application/Home/View/Public/img/file/fla.png b/Application/Home/View/Public/img/file/fla.png
new file mode 100644
index 00000000..2d4a0d52
Binary files /dev/null and b/Application/Home/View/Public/img/file/fla.png differ
diff --git a/Application/Home/View/Public/img/file/gif.png b/Application/Home/View/Public/img/file/gif.png
new file mode 100644
index 00000000..a3ca9714
Binary files /dev/null and b/Application/Home/View/Public/img/file/gif.png differ
diff --git a/Application/Home/View/Public/img/file/html.png b/Application/Home/View/Public/img/file/html.png
new file mode 100644
index 00000000..ca811ee4
Binary files /dev/null and b/Application/Home/View/Public/img/file/html.png differ
diff --git a/Application/Home/View/Public/img/file/ind.png b/Application/Home/View/Public/img/file/ind.png
new file mode 100644
index 00000000..0d37e703
Binary files /dev/null and b/Application/Home/View/Public/img/file/ind.png differ
diff --git a/Application/Home/View/Public/img/file/ini.png b/Application/Home/View/Public/img/file/ini.png
new file mode 100644
index 00000000..0933f976
Binary files /dev/null and b/Application/Home/View/Public/img/file/ini.png differ
diff --git a/Application/Home/View/Public/img/file/jpeg.png b/Application/Home/View/Public/img/file/jpeg.png
new file mode 100644
index 00000000..71af44b7
Binary files /dev/null and b/Application/Home/View/Public/img/file/jpeg.png differ
diff --git a/Application/Home/View/Public/img/file/jsf.png b/Application/Home/View/Public/img/file/jsf.png
new file mode 100644
index 00000000..52fe4d36
Binary files /dev/null and b/Application/Home/View/Public/img/file/jsf.png differ
diff --git a/Application/Home/View/Public/img/file/midi.png b/Application/Home/View/Public/img/file/midi.png
new file mode 100644
index 00000000..d0a553b4
Binary files /dev/null and b/Application/Home/View/Public/img/file/midi.png differ
diff --git a/Application/Home/View/Public/img/file/mov.png b/Application/Home/View/Public/img/file/mov.png
new file mode 100644
index 00000000..a66b1e31
Binary files /dev/null and b/Application/Home/View/Public/img/file/mov.png differ
diff --git a/Application/Home/View/Public/img/file/mp3.png b/Application/Home/View/Public/img/file/mp3.png
new file mode 100644
index 00000000..a7f1831d
Binary files /dev/null and b/Application/Home/View/Public/img/file/mp3.png differ
diff --git a/Application/Home/View/Public/img/file/mpeg.png b/Application/Home/View/Public/img/file/mpeg.png
new file mode 100644
index 00000000..44b132dc
Binary files /dev/null and b/Application/Home/View/Public/img/file/mpeg.png differ
diff --git a/Application/Home/View/Public/img/file/pdf.png b/Application/Home/View/Public/img/file/pdf.png
new file mode 100644
index 00000000..7f67ab61
Binary files /dev/null and b/Application/Home/View/Public/img/file/pdf.png differ
diff --git a/Application/Home/View/Public/img/file/png.png b/Application/Home/View/Public/img/file/png.png
new file mode 100644
index 00000000..cf1c63e7
Binary files /dev/null and b/Application/Home/View/Public/img/file/png.png differ
diff --git a/Application/Home/View/Public/img/file/ppt.png b/Application/Home/View/Public/img/file/ppt.png
new file mode 100644
index 00000000..c46af4cf
Binary files /dev/null and b/Application/Home/View/Public/img/file/ppt.png differ
diff --git a/Application/Home/View/Public/img/file/pptx.png b/Application/Home/View/Public/img/file/pptx.png
new file mode 100644
index 00000000..a87d4653
Binary files /dev/null and b/Application/Home/View/Public/img/file/pptx.png differ
diff --git a/Application/Home/View/Public/img/file/proj.png b/Application/Home/View/Public/img/file/proj.png
new file mode 100644
index 00000000..a71f2041
Binary files /dev/null and b/Application/Home/View/Public/img/file/proj.png differ
diff --git a/Application/Home/View/Public/img/file/psd.png b/Application/Home/View/Public/img/file/psd.png
new file mode 100644
index 00000000..67bd3253
Binary files /dev/null and b/Application/Home/View/Public/img/file/psd.png differ
diff --git a/Application/Home/View/Public/img/file/pst.png b/Application/Home/View/Public/img/file/pst.png
new file mode 100644
index 00000000..d64af49c
Binary files /dev/null and b/Application/Home/View/Public/img/file/pst.png differ
diff --git a/Application/Home/View/Public/img/file/pub.png b/Application/Home/View/Public/img/file/pub.png
new file mode 100644
index 00000000..4b54b44f
Binary files /dev/null and b/Application/Home/View/Public/img/file/pub.png differ
diff --git a/Application/Home/View/Public/img/file/rar.png b/Application/Home/View/Public/img/file/rar.png
new file mode 100644
index 00000000..dbeb2e9e
Binary files /dev/null and b/Application/Home/View/Public/img/file/rar.png differ
diff --git a/Application/Home/View/Public/img/file/read.png b/Application/Home/View/Public/img/file/read.png
new file mode 100644
index 00000000..42c68efe
Binary files /dev/null and b/Application/Home/View/Public/img/file/read.png differ
diff --git a/Application/Home/View/Public/img/file/set.png b/Application/Home/View/Public/img/file/set.png
new file mode 100644
index 00000000..06efe6b8
Binary files /dev/null and b/Application/Home/View/Public/img/file/set.png differ
diff --git a/Application/Home/View/Public/img/file/tiff.png b/Application/Home/View/Public/img/file/tiff.png
new file mode 100644
index 00000000..48faa5c0
Binary files /dev/null and b/Application/Home/View/Public/img/file/tiff.png differ
diff --git a/Application/Home/View/Public/img/file/txt.png b/Application/Home/View/Public/img/file/txt.png
new file mode 100644
index 00000000..9d82134d
Binary files /dev/null and b/Application/Home/View/Public/img/file/txt.png differ
diff --git a/Application/Home/View/Public/img/file/url.png b/Application/Home/View/Public/img/file/url.png
new file mode 100644
index 00000000..17fbbd3b
Binary files /dev/null and b/Application/Home/View/Public/img/file/url.png differ
diff --git a/Application/Home/View/Public/img/file/vsd.png b/Application/Home/View/Public/img/file/vsd.png
new file mode 100644
index 00000000..cd415f12
Binary files /dev/null and b/Application/Home/View/Public/img/file/vsd.png differ
diff --git a/Application/Home/View/Public/img/file/wav.png b/Application/Home/View/Public/img/file/wav.png
new file mode 100644
index 00000000..306a7a1f
Binary files /dev/null and b/Application/Home/View/Public/img/file/wav.png differ
diff --git a/Application/Home/View/Public/img/file/wma.png b/Application/Home/View/Public/img/file/wma.png
new file mode 100644
index 00000000..632f32e6
Binary files /dev/null and b/Application/Home/View/Public/img/file/wma.png differ
diff --git a/Application/Home/View/Public/img/file/wmv.png b/Application/Home/View/Public/img/file/wmv.png
new file mode 100644
index 00000000..0a8fee95
Binary files /dev/null and b/Application/Home/View/Public/img/file/wmv.png differ
diff --git a/Application/Home/View/Public/img/file/xls.png b/Application/Home/View/Public/img/file/xls.png
new file mode 100644
index 00000000..cd8fc011
Binary files /dev/null and b/Application/Home/View/Public/img/file/xls.png differ
diff --git a/Application/Home/View/Public/img/file/xlsx.png b/Application/Home/View/Public/img/file/xlsx.png
new file mode 100644
index 00000000..656b18cb
Binary files /dev/null and b/Application/Home/View/Public/img/file/xlsx.png differ
diff --git a/Application/Home/View/Public/img/file/zip.png b/Application/Home/View/Public/img/file/zip.png
new file mode 100644
index 00000000..91224e6d
Binary files /dev/null and b/Application/Home/View/Public/img/file/zip.png differ
diff --git a/Application/Home/View/Public/img/index/index_data_point.png b/Application/Home/View/Public/img/index/index_data_point.png
new file mode 100644
index 00000000..d46d1c28
Binary files /dev/null and b/Application/Home/View/Public/img/index/index_data_point.png differ
diff --git a/Application/Home/View/Public/img/index/index_device.png b/Application/Home/View/Public/img/index/index_device.png
new file mode 100644
index 00000000..4d770f91
Binary files /dev/null and b/Application/Home/View/Public/img/index/index_device.png differ
diff --git a/Application/Home/View/Public/img/index/login-left.png b/Application/Home/View/Public/img/index/login-left.png
new file mode 100644
index 00000000..193d5177
Binary files /dev/null and b/Application/Home/View/Public/img/index/login-left.png differ
diff --git a/Application/Home/View/Public/js/home.js b/Application/Home/View/Public/js/home.js
new file mode 100644
index 00000000..49226423
--- /dev/null
+++ b/Application/Home/View/Public/js/home.js
@@ -0,0 +1,234 @@
+$(function(){
+ // 一次性初始化所有弹出框
+ $('[data-toggle="popover"]').popover();
+
+ // 图片lazyload
+ $('img.lazy').lazyload({
+ effect : 'fadeIn',
+ data_attribute : 'src',
+ placeholder : OpenCMF.DEFAULT_IMG
+ });
+
+
+ // Ajax全局设置
+ $.ajaxSetup({
+ timeout: 5000,
+ dataType: 'json',
+ xhrFields: {
+ withCredentials: true // 跨域请求携带凭证
+ }
+ });
+
+
+ //全选/反选/单选的实现
+ $(document).on('click', '.check-all', function() {
+ $(".ids").prop("checked", this.checked);
+ });
+
+ $(document).on('click', '.ids', function() {
+ var option = $(".ids");
+ option.each(function() {
+ if (!this.checked) {
+ $(".check-all").prop("checked", false);
+ return false;
+ } else {
+ $(".check-all").prop("checked", true);
+ }
+ });
+ });
+
+ //搜索功能
+ $(document).on('click', '.search-btn', function() {
+ var url = $(this).closest('form').attr('action');
+ var query = $(this).closest('form').serialize();
+ query = query.replace(/(&|^)(\w*?\d*?\-*?_*?)*?=?((?=&)|(?=$))/g, '');
+ query = query.replace(/(^&)|(\+)/g, '');
+ if (url.indexOf('?') > 0) {
+ url += '&' + query;
+ } else {
+ url += '?' + query;
+ }
+ window.location.href = url;
+ return false;
+ });
+
+ //回车搜索
+ $(document).on('keydown', '.search-input', function(e) {
+ if (e.keyCode === 13) {
+ $(this).closest('form').find('.search-btn').click();
+ return false;
+ }
+ });
+
+ /*
+ * 改变URL参数
+ * url 目标url
+ * arg 需要替换的参数名称
+ * arg_val 替换后的参数的值
+ * return url 参数替换后的url
+ */
+ function change_url_parameter(destiny, par, par_value) {
+ var pattern = par+'=([^&]*)';
+ var replaceText = par+'='+par_value;
+ if (destiny.match(pattern)) {
+ var tmp='/('+ par+'=)([^&]*)/gi';
+ tmp = destiny.replace(eval(tmp), replaceText);
+ return (tmp);
+ } else {
+ if (destiny.match('[\?]')) {
+ return destiny+'&'+ replaceText;
+ } else {
+ return destiny+'?'+replaceText;
+ }
+ }
+ return destiny+'\n'+par+'\n'+par_value;
+ }
+
+ // 多条件筛选
+ $('body').delegate('a.query-link', 'click', function() {
+ var url = window.location.href;
+ var data_name = $(this).attr('data-name');
+ var data_value = $(this).attr('data-value');
+ url = change_url_parameter(url, data_name, data_value);
+ window.location.href = url;
+ return false;
+ });
+
+
+ // 刷新验证码
+ $(document).on('click', '.reload-verify', function() {
+ var verifyimg = $(this).attr("src");
+ if (verifyimg.indexOf('?') > 0) {
+ $(this).attr("src", verifyimg + '&random=' + Math.random());
+ } else {
+ $(this).attr("src", verifyimg.replace(/\?.*$/, '') + '?' + Math.random());
+ }
+ });
+
+ //发送验证码倒计时
+ function time(that, wait){
+ if (wait == 0) {
+ $(that).removeClass('disabled').prop('disabled',false);
+ $(that).html('重新发送验证码');
+ } else {
+ $(that).html(wait+'秒后重新发送');
+ wait--;
+ setTimeout(function(){
+ time(that, wait);
+ }, 1000);
+ }
+ }
+
+ // 发送邮件验证码
+ $(document).on('click', '.send-mail-verify', function() {
+ var url = OpenCMF.TOP_HOME_PAGE + '/index.php?s=/user/user/send_mail_verify';
+ var that = this;
+ var title = $(that).attr('data-title');
+ var email = $(that).closest('form').find('input[name="email"]').val();
+ var filter = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
+ if (!filter.test(email)) {
+ $.alertMessager('邮箱账号不正确', 'danger');
+ $(that).addClass('disabled').prop('disabled', true);
+ time(that, 5);
+ } else {
+ $(that).addClass('disabled').prop('disabled', true);
+ time(that, 1);
+ $.ajax({
+ url:url,
+ data:{'email': email,'title': title},
+ type:'POST',
+ success:function(data){
+ if (data.status == 1) {
+ $.alertMessager(data.info, 'success');
+ } else {
+ $.alertMessager(data.info, 'danger');
+ }
+ },
+ error: function(e) {
+ if (e.responseText) {
+ alert(e.responseText);
+ }
+ $(that).removeClass('disabled').prop('disabled', false);
+ }
+ });
+ }
+ return false;
+ });
+
+ // 发送短信验证码
+ $(document).on('click', '.send-mobile-verify', function() {
+ var url = OpenCMF.TOP_HOME_PAGE + '/index.php?s=/user/user/send_mobile_verify';
+ var that = this;
+ var title = $(that).attr('data-title');
+ var mobile = $(this).closest('form').find('input[name="mobile"]').val();
+ var filter = /^1\d{10}$/;
+ if (!filter.test(mobile)) {
+ $.alertMessager('手机号码不正确', 'danger');
+ $(that).addClass('disabled').prop('disabled', true);
+ time(that, 5);
+ } else {
+ $(that).addClass('disabled').prop('disabled', true);
+ time(that, 30);
+ $.ajax({
+ url:url,
+ data:{'mobile': mobile,'title': title},
+ type:'POST',
+ success:function(data){
+ if (data.status == 1) {
+ $.alertMessager(data.info, 'success');
+ } else {
+ $.alertMessager(data.info, 'danger');
+ }
+ },
+ error: function(e) {
+ if (e.responseText) {
+ alert(e.responseText);
+ }
+ $(that).removeClass('disabled').prop('disabled', false);
+ }
+ });
+ }
+ return false;
+ });
+
+
+ // 获取GPS定位
+ function get_location(){
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(get_location_success, get_location_error);
+ } else {
+ $.alertMessager('浏览器不支持地理定位', 'danger');
+ }
+ }
+
+ // 获取GPS定位存储进cookie,后台用$_COOKIE['oc_latitude']和$_COOKIE['oc_longitude']读取
+ // 如果需要用于GPS距离计算直接使用存储的坐标即可,如果需要在百度地图定位则需要手动调用百度地图接口进行坐标转换
+ function get_location_success(position){
+ var lat = position.coords.latitude; //纬度
+ var lng = position.coords.longitude; //经度
+ $.cookie('oc_latitude', lat, {path: OpenCMF.VAR_ROOT});
+ $.cookie('oc_longitude', lng, {path: OpenCMF.VAR_ROOT});
+ }
+
+ // 获取GPS定位错误提示
+ function get_location_error(error){
+ switch(error.code) {
+ case error.PERMISSION_DENIED:
+ //$.alertMessager('定位失败,用户拒绝请求地理定位', 'danger');
+ break;
+ case error.POSITION_UNAVAILABLE:
+ //$.alertMessager('定位失败,位置信息是不可用', 'danger');
+ break;
+ case error.TIMEOUT:
+ //$.alertMessager('定位失败,请求获取用户位置超时', 'danger');
+ break;
+ case error.UNKNOWN_ERROR:
+ //$.alertMessager('定位失败,定位系统失效', 'danger');
+ break;
+ }
+ }
+
+ // 请求定位
+ //get_location();
+ //window.setInterval(get_location, 20000);
+});
diff --git a/Application/Home/View/Public/layout.html b/Application/Home/View/Public/layout.html
new file mode 100644
index 00000000..06bd87ea
--- /dev/null
+++ b/Application/Home/View/Public/layout.html
@@ -0,0 +1,215 @@
+
+
+
+
+ {$meta_title}|{:C('WEB_SITE_TITLE')}-{:C('WEB_SITE_SLOGAN')}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
简单、高效、卓越 - {:C('WEB_SITE_SLOGAN')}
+
+
+
+
+
+
+
+
+ 首页
+
+ {$meta_title}
+
+
+
+
+
+
+ 这里是内容
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {:C('WEB_SITE_STATISTICS')}
+
+
+
+
+
diff --git a/Application/Home/View/Public/think/error.html b/Application/Home/View/Public/think/error.html
new file mode 100644
index 00000000..07fbfa04
--- /dev/null
+++ b/Application/Home/View/Public/think/error.html
@@ -0,0 +1,145 @@
+
+
+
+
+ 跳转提示
+
+
+
+
+
+
+
+
友情提醒!
+
+
+
+ 秒后页面将自动跳转
+
+
+
+
+
+
+
diff --git a/Application/Home/View/Public/think/exception.html b/Application/Home/View/Public/think/exception.html
new file mode 100644
index 00000000..57636498
--- /dev/null
+++ b/Application/Home/View/Public/think/exception.html
@@ -0,0 +1,58 @@
+
+
+
+
+系统发生错误
+
+
+
+
+
+
OpenCMF 1.4.0 { Simple Efficient Excellent } -- [ WE CAN MAKE IT EASY ]
+
+
+
diff --git a/Application/Home/View/Public/think/success.html b/Application/Home/View/Public/think/success.html
new file mode 100644
index 00000000..82e84cf8
--- /dev/null
+++ b/Application/Home/View/Public/think/success.html
@@ -0,0 +1,144 @@
+
+
+
+
+ 跳转提示
+
+
+
+
+
+
+
+
恭喜您!
+
+
+
+ 秒后页面将自动跳转
+
+
+
+
+
+
+
diff --git a/Application/Install/Common/function.php b/Application/Install/Common/function.php
new file mode 100644
index 00000000..70f790e0
--- /dev/null
+++ b/Application/Install/Common/function.php
@@ -0,0 +1,261 @@
+
+// +----------------------------------------------------------------------
+
+/**
+ * 系统环境检测
+ * @return array 系统环境数据
+ * @author jry <598821125@qq.com>
+ */
+function check_env() {
+ $items = array(
+ 'os' => array(
+ 'title' => '操作系统',
+ 'limit' => '不限制',
+ 'current' => PHP_OS,
+ 'icon' => 'fa-check text-success',
+ ),
+ 'php' => array(
+ 'title' => 'PHP版本',
+ 'limit' => '5.3+',
+ 'current' => PHP_VERSION,
+ 'icon' => 'fa-check text-success',
+ ),
+ 'upload' => array(
+ 'title' => '附件上传',
+ 'limit' => '不限制',
+ 'current' => ini_get('file_uploads') ? ini_get('upload_max_filesize'):'未知',
+ 'icon' => 'fa-check text-success',
+ ),
+ 'gd' => array(
+ 'title' => 'GD库',
+ 'limit' => '2.0+',
+ 'current' => '未知',
+ 'icon' => 'fa-check text-success',
+ ),
+ 'disk' => array(
+ 'title' => '磁盘空间',
+ 'limit' => '100M+',
+ 'current' => '未知',
+ 'icon' => 'fa-check text-success',
+ ),
+ );
+
+ //PHP环境检测
+ if ($items['php']['current'] < 5.3) {
+ $items['php']['icon'] = 'fa-remove text-danger';
+ session('error', true);
+ }
+
+ //GD库检测
+ $tmp = function_exists('gd_info') ? gd_info() : array();
+ if (!$tmp['GD Version']) {
+ $items['gd']['current'] = '未安装';
+ $items['gd']['icon'] = 'fa-remove text-danger';
+ session('error', true);
+ } else {
+ $items['gd']['current'] = $tmp['GD Version'];
+ }
+ unset($tmp);
+
+ //磁盘空间检测
+ if (function_exists('disk_free_space')) {
+ $disk_size = floor(disk_free_space('./') / (1024*1024)).'M';
+ $items['disk']['current'] = $disk_size.'MB';
+ if ($disk_size < 100) {
+ $items['disk']['icon'] = 'fa-remove text-danger';
+ session('error', true);
+ }
+ }
+
+ return $items;
+}
+
+/**
+ * 目录,文件读写检测
+ * @return array 检测数据
+ * @author jry <598821125@qq.com>
+ */
+function check_dirfile() {
+ $items = array(
+ '0' => array(
+ 'type' => 'dir',
+ 'path' => RUNTIME_PATH,
+ 'title' => '可写',
+ 'icon' => 'fa-check text-success',
+ ),
+ '1' => array(
+ 'type' => 'dir',
+ 'path' => './Uploads',
+ 'title' => '可写',
+ 'icon' => 'fa-check text-success',
+ ),
+ '2' => array(
+ 'type' => 'dir',
+ 'path' => './Data',
+ 'title' => '可写',
+ 'icon' => 'fa-check text-success',
+ ),
+ );
+
+ foreach ($items as &$val) {
+ $path = $val['path'];
+ if ('dir' === $val['type']) {
+ if (!is_writable($path)) {
+ if (is_dir($path)) {
+ $val['title'] = '不可写';
+ $val['icon'] = 'fa-remove text-danger';
+ session('error', true);
+ } else {
+ $val['title'] = '不存在';
+ $val['icon'] = 'fa-remove text-danger';
+ session('error', true);
+ }
+ }
+ } else {
+ if (file_exists($path)) {
+ if (!is_writable($path)) {
+ $val['title'] = '不可写';
+ $val['icon'] = 'fa-remove text-danger';
+ session('error', true);
+ }
+ } else {
+ if (!is_writable(dirname($path))) {
+ $val['title'] = '不存在';
+ $val['icon'] = 'fa-remove text-danger';
+ session('error', true);
+ }
+ }
+ }
+ }
+ return $items;
+}
+
+/**
+ * 函数检测
+ * @return array 检测数据
+ */
+function check_func_and_ext() {
+ $items = array(
+ '0' => array(
+ 'type' => 'ext',
+ 'name' => 'pdo',
+ 'title' => '支持',
+ 'current' => extension_loaded('pdo'),
+ 'icon' => 'fa-check text-success',
+ ),
+ '1' => array(
+ 'type' => 'ext',
+ 'name' => 'pdo_mysql',
+ 'title' => '支持',
+ 'current' => extension_loaded('pdo_mysql'),
+ 'icon' => 'fa-check text-success',
+ ),
+ '2' => array(
+ 'type' => 'func',
+ 'name' => 'file_get_contents',
+ 'title' => '支持',
+ 'icon' => 'fa-check text-success',
+ ),
+ '3' => array(
+ 'type' => 'func',
+ 'name' => 'mb_strlen',
+ 'title' => '支持',
+ 'icon' => 'fa-check text-success',
+ ),
+ );
+ foreach ($items as &$val) {
+ switch ($val['type']) {
+ case 'ext':
+ if (!$val['current']) {
+ $val['title'] = '不支持';
+ $val['icon'] = 'fa-remove text-danger';
+ session('error', true);
+ }
+ break;
+ case 'func':
+ if (!function_exists($val['name'])) {
+ $val['title'] = '不支持';
+ $val['icon'] = 'fa-remove text-danger';
+ session('error', true);
+ }
+ break;
+ }
+ }
+
+ return $items;
+}
+
+/**
+ * 创建数据表
+ * @param resource $db 数据库连接资源
+ */
+function create_tables($db, $prefix = '') {
+ //读取SQL文件
+ $sql = file_get_contents(MODULE_PATH . 'Data/install.sql');
+ $sql = str_replace("\r", "\n", $sql);
+ $sql = explode(";\n", $sql);
+
+ //替换表前缀
+ $orginal = C('ORIGINAL_TABLE_PREFIX');
+ $sql = str_replace(" `{$orginal}", " `{$prefix}", $sql);
+
+ //开始安装
+ show_msg('开始安装数据库...');
+ foreach ($sql as $value) {
+ $value = trim($value);
+ if (empty($value)) continue;
+ if (substr($value, 0, 12) == 'CREATE TABLE') {
+ $name = preg_replace("/^CREATE TABLE `(\w+)` .*/s", "\\1", $value);
+ $msg = "创建数据表{$name}";
+ if (false !== $db->execute($value)) {
+ show_msg($msg . '...成功');
+ } else {
+ show_msg($msg . '...失败!', 'error');
+ session('error', true);
+ }
+ } else {
+ $db->execute($value);
+ }
+ }
+}
+
+/**
+ * 写入配置文件
+ * @param array $config 配置信息
+ */
+function write_config($config, $auth) {
+ if (is_array($config)) {
+ //读取配置内容
+ $conf = file_get_contents(MODULE_PATH . 'Data/config.tpl');
+ //替换配置项
+ foreach ($config as $name => $value) {
+ $conf = str_replace("[{$name}]", $value, $conf);
+ }
+ $conf = str_replace('[AUTH_KEY]', $auth, $conf);
+ //写入应用配置文件
+
+ if (file_put_contents('./Data/db.php', $conf)) {
+ show_msg('配置文件写入成功');
+ } else {
+ show_msg('配置文件写入失败!', 'error');
+ session('error', true);
+ }
+ return true;
+ }
+}
+
+/**
+ * 及时显示提示信息
+ * @param string $msg 提示信息
+ */
+function show_msg($msg, $class = '') {
+ echo "";
+ flush();
+ ob_flush();
+}
diff --git a/Application/Install/Conf/config.php b/Application/Install/Conf/config.php
new file mode 100644
index 00000000..98c666a6
--- /dev/null
+++ b/Application/Install/Conf/config.php
@@ -0,0 +1,38 @@
+
+// +----------------------------------------------------------------------
+/**
+ * 安装程序配置文件
+ */
+return array(
+ //产品配置
+ 'INSTALL_PRODUCT_NAME' => 'lyadmin', //产品名称
+ 'INSTALL_WEBSITE_DOMAIN' => 'http://lyadmin.lyunweb.com', //官方网址
+ 'INSTALL_COMPANY_NAME' => '南京科斯克网络科技有限公司', //公司名称
+ 'ORIGINAL_TABLE_PREFIX' => 'ly_', //默认表前缀
+
+ //模板相关配置
+ 'TMPL_PARSE_STRING' => array(
+ '__PUBLIC__' => __ROOT__.'/Public',
+ '__LYUI__' => __ROOT__.'/Public/libs/lyui/dist',
+ '__IMG__' => __ROOT__.'/Application/'.MODULE_NAME.'/View/Public/img',
+ '__CSS__' => __ROOT__.'/Application/'.MODULE_NAME.'/View/Public/css',
+ '__JS__' => __ROOT__.'/Application/'.MODULE_NAME.'/View/Public/js',
+ ),
+
+ //前缀设置避免冲突
+ 'DATA_CACHE_PREFIX' => ENV_PRE.MODULE_NAME.'_', //缓存前缀
+ 'SESSION_PREFIX' => ENV_PRE.MODULE_NAME.'_', //Session前缀
+ 'COOKIE_PREFIX' => ENV_PRE.MODULE_NAME.'_', //Cookie前缀
+
+ //是否开启模板编译缓存,设为false则每次都会重新编译
+ 'TMPL_CACHE_ON' => false,
+
+ // 默认模块
+ 'DEFAULT_MODULE' => 'Install',
+);
diff --git a/Application/Install/Controller/IndexController.class.php b/Application/Install/Controller/IndexController.class.php
new file mode 100644
index 00000000..bead6040
--- /dev/null
+++ b/Application/Install/Controller/IndexController.class.php
@@ -0,0 +1,176 @@
+
+// +----------------------------------------------------------------------
+namespace Install\Controller;
+use Think\Controller;
+use Think\Db;
+use Think\Storage;
+use Common\Util\Think\Str;
+/**
+ * 安装控制器
+ */
+class IndexController extends Controller{
+ // 初始化方法
+ protected function _initialize() {
+ $no_verify = array('index', 'step1', 'complete');
+ if (in_array(ACTION_NAME, $no_verify)) {
+ return true;
+ }
+ if (Storage::has('./Data/install.lock')) {
+ $this->error('已经成功安装了本系统,请不要重复安装!', U('Home/Index/index'));
+ } else if ($_SERVER[ENV_PRE.'DEV_MODE'] === 'true') {
+ $this->error('系统处于开发模式,无需安装!', U('Home/Index/index'));
+ }
+ }
+
+ // 安装首页
+ public function index() {
+ $this->redirect('step1');
+ }
+
+ // 安装第一步,同意安装协议
+ public function step1() {
+ session('step', '1');
+ session('error', false);
+ $this->assign('meta_title', "step1");
+ $this->display();
+ }
+
+ // 安装第二步,检测运行所需的环境设置
+ public function step2() {
+ if (IS_AJAX) {
+ if (session('error')) {
+ $this->error('环境检测没有通过,请调整环境后重试!');
+ } else {
+ $this->success('恭喜您环境检测通过', U('step3'));
+ }
+ } else {
+ session('step', '2');
+ session('error', false);
+
+ //环境检测
+ $this->assign('check_env', check_env());
+
+ //目录文件读写检测
+ if (IS_WRITE) {
+ $this->assign('check_dirfile', check_dirfile());
+ }
+
+ //函数及扩展库检测
+ $this->assign('check_func_and_ext', check_func_and_ext());
+
+ $this->assign('meta_title', "step2");
+ $this->display();
+ }
+ }
+
+ // 安装第三步,创建数据库
+ public function step3($db = null) {
+ if (IS_POST) {
+ //检测数据库配置
+ if (!is_array($db) || empty($db['DB_TYPE'])
+ || empty($db['DB_HOST']) || empty($db['DB_NAME'])
+ || empty($db['DB_USER']) || empty($db['DB_PREFIX'])){
+ $this->error('请填写完整的数据库配置');
+ } else {
+ //缓存数据库配置
+ session('db_config', $db);
+
+ //创建数据库连接
+ $db_name = $db['DB_NAME'];
+ unset($db['DB_NAME']); //防止不存在的数据库导致连接数据库失败
+ $db_instance = Db::getInstance($db);
+
+ //检测数据库连接
+ $result1 = $db_instance->execute('select version()');
+ if (!$result1) {
+ $this->error('数据库连接失败,请检查数据库配置!');
+ }
+
+ //用户选择不覆盖情况下检测是否已存在数据库
+ if (I('post.cover') === '0') {
+ //检测是否已存在数据库
+ $result2 = $db_instance->execute('SELECT * FROM information_schema.schemata WHERE schema_name="'.$db_name.'"');
+ if ($result2) {
+ $this->error('该数据库已存在,请更换名称!如需覆盖,请选中覆盖按钮!');
+ }
+ }
+
+ //创建数据库
+ $sql = "CREATE DATABASE IF NOT EXISTS `{$db_name}` DEFAULT CHARACTER SET utf8";
+ $db_instance->execute($sql) || $this->error($db_instance->getError());
+ }
+
+ //跳转到数据库安装页面
+ $this->success('参数正确开始安装', U('step4'));
+ } else {
+ session('step', '3');
+ session('error', false);
+ $rand = Str::randString(6,3); //生成随机数
+ $this->assign('meta_title', "step3");
+ $this->display();
+ }
+ }
+
+ // 安装第四步,安装数据表,创建配置文件
+ public function step4() {
+ if (session('step') !== '3') {
+ $this->error('请按顺序安装', U('step3'));
+ }
+ session('step', '4');
+ session('error', false);
+ $this->assign('meta_title', "step4");
+ $this->display();
+
+ //连接数据库
+ $db_config = session('db_config');
+ $db_instance = Db::getInstance($db_config);
+
+ //创建数据表
+ create_tables($db_instance, $db_config['DB_PREFIX']);
+
+ //生成加密字符串
+ $add_chars .= '`~!@#$%^&*()_+-=[]{};:"|,.<>/?';
+ $auth = Str::randString(64, '', $add_chars); //生成随机数
+
+ //创建配置文件
+ $conf = write_config($db_config, $auth);
+
+ //根据加密字符串更新admin密码的加密结果
+ $new_admin_password = user_md5('admin', $auth);
+ $sql = <<execute($sql);
+ if (!$result) {
+ $this->error('写入系统加密KEY或管理员新密码出错!');
+ }
+
+ if (session('error')) {
+ $this->error('安装出错', 'index');
+ } else {
+ $this->redirect('complete');
+ }
+ }
+
+ // 安装完成
+ public function complete() {
+ if (session('step') !== '4') {
+ $this->error('请正确安装系统', U('step1'));
+ }
+
+ //写入安装锁定文件(只能在最后一步写入锁定文件,因为锁定文件写入后安装模块将无法访问)
+ Storage::put('./Data/install.lock', 'lock');
+
+ session('step', null);
+ session('error', null);
+ $this->assign('meta_title', "完成");
+ $this->display();
+ }
+}
diff --git a/Application/Install/Data/config.tpl b/Application/Install/Data/config.tpl
new file mode 100644
index 00000000..82f64f95
--- /dev/null
+++ b/Application/Install/Data/config.tpl
@@ -0,0 +1,37 @@
+
+// +----------------------------------------------------------------------
+
+/**
+ * CoreThink数据库连接配置文件
+ */
+
+// 开启开发部署模式
+if (@$_SERVER[ENV_PRE.'DEV_MODE'] === 'true') {
+ // 数据库配置
+ return array(
+ 'DB_TYPE' => $_SERVER[ENV_PRE.'DB_TYPE'] ? : '[DB_TYPE]', // 数据库类型
+ 'DB_HOST' => $_SERVER[ENV_PRE.'DB_HOST'] ? : '[DB_HOST]', // 服务器地址
+ 'DB_NAME' => $_SERVER[ENV_PRE.'DB_NAME'] ? : '[DB_NAME]', // 数据库名
+ 'DB_USER' => $_SERVER[ENV_PRE.'DB_USER'] ? : '[DB_USER]', // 用户名
+ 'DB_PWD' => $_SERVER[ENV_PRE.'DB_PWD'] ? : '[DB_PWD]', // 密码
+ 'DB_PORT' => $_SERVER[ENV_PRE.'DB_PORT'] ? : '3306', // 端口
+ 'DB_PREFIX' => $_SERVER[ENV_PRE.'DB_PREFIX'] ? : '[DB_PREFIX]', // 数据库表前缀
+ );
+} else {
+ // 数据库配置
+ return array(
+ 'DB_TYPE' => '[DB_TYPE]', // 数据库类型
+ 'DB_HOST' => '[DB_HOST]', // 服务器地址
+ 'DB_NAME' => '[DB_NAME]', // 数据库名
+ 'DB_USER' => '[DB_USER]', // 用户名
+ 'DB_PWD' => '[DB_PWD]', // 密码
+ 'DB_PORT' => '3306', // 端口
+ 'DB_PREFIX' => '[DB_PREFIX]', // 数据库表前缀
+ );
+}
diff --git a/Application/Install/Data/install.sql b/Application/Install/Data/install.sql
new file mode 100644
index 00000000..bf0691c9
--- /dev/null
+++ b/Application/Install/Data/install.sql
@@ -0,0 +1,282 @@
+# Host: localhost (Version: 5.5.40)
+# Date: 2016-10-18 00:40:41
+# Generator: MySQL-Front 5.3 (Build 4.120)
+
+/*!40101 SET NAMES utf8 */;
+
+#
+# Structure for table "ly_admin_access"
+#
+
+DROP TABLE IF EXISTS `ly_admin_access`;
+CREATE TABLE `ly_admin_access` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '管理员ID',
+ `uid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID',
+ `group` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '管理员用户组',
+ `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
+ `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
+ `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
+ `status` tinyint(3) NOT NULL DEFAULT '0' COMMENT '状态',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='后台管理员与用户组对应关系表';
+
+#
+# Data for table "ly_admin_access"
+#
+
+INSERT INTO `ly_admin_access` VALUES (1,1,1,1438651748,1438651748,0,1);
+
+#
+# Structure for table "ly_admin_addon"
+#
+
+DROP TABLE IF EXISTS `ly_admin_addon`;
+CREATE TABLE `ly_admin_addon` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `name` varchar(32) DEFAULT NULL COMMENT '插件名或标识',
+ `title` varchar(32) NOT NULL DEFAULT '' COMMENT '中文名',
+ `description` text NOT NULL COMMENT '插件描述',
+ `config` text COMMENT '配置',
+ `author` varchar(32) NOT NULL DEFAULT '' COMMENT '作者',
+ `version` varchar(8) NOT NULL DEFAULT '' COMMENT '版本号',
+ `adminlist` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '是否有后台列表',
+ `type` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '插件类型',
+ `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '安装时间',
+ `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '修改时间',
+ `sort` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
+ `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='插件表';
+
+#
+# Data for table "ly_admin_addon"
+#
+
+INSERT INTO `ly_admin_addon` VALUES (1,'RocketToTop','小火箭返回顶部','小火箭返回顶部','{\"status\":\"1\"}','OpenCMF','1.3.0',0,0,1476718525,1476718525,0,1);
+
+#
+# Structure for table "ly_admin_config"
+#
+
+DROP TABLE IF EXISTS `ly_admin_config`;
+CREATE TABLE `ly_admin_config` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '配置ID',
+ `title` varchar(32) NOT NULL DEFAULT '' COMMENT '配置标题',
+ `name` varchar(32) DEFAULT NULL COMMENT '配置名称',
+ `value` text NOT NULL COMMENT '配置值',
+ `group` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '配置分组',
+ `type` varchar(16) NOT NULL DEFAULT '' COMMENT '配置类型',
+ `options` varchar(255) NOT NULL DEFAULT '' COMMENT '配置额外值',
+ `tip` varchar(100) NOT NULL DEFAULT '' COMMENT '配置说明',
+ `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
+ `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
+ `sort` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
+ `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8 COMMENT='系统配置表';
+
+#
+# Data for table "ly_admin_config"
+#
+
+INSERT INTO `ly_admin_config` VALUES (1,'站点开关','TOGGLE_WEB_SITE','1',1,'select','0:关闭\r\n1:开启','站点关闭后将不能访问',1378898976,1406992386,1,1),(2,'网站标题','WEB_SITE_TITLE','lyadmin',1,'text','','网站标题前台显示标题',1378898976,1379235274,2,1),(3,'网站口号','WEB_SITE_SLOGAN','轻量级通用后台',1,'text','','网站口号、宣传标语、一句话介绍',1434081649,1434081649,3,1),(4,'网站LOGO','WEB_SITE_LOGO','',1,'picture','','网站LOGO',1407003397,1407004692,4,1),(5,'网站反色LOGO','WEB_SITE_LOGO_INVERSE','',1,'picture','','匹配深色背景上的LOGO',1476700797,1476700797,5,1),(6,'网站描述','WEB_SITE_DESCRIPTION','lyadmin是一套轻量级通用后台,追求简单、高效、卓越。',1,'textarea','','网站搜索引擎描述',1378898976,1379235841,6,1),(7,'网站关键字','WEB_SITE_KEYWORD','kyadmin 、南京科斯克网络科技',1,'textarea','','网站搜索引擎关键字',1378898976,1381390100,7,1),(8,'版权信息','WEB_SITE_COPYRIGHT','Copyright © 南京科斯克网络科技有限公司 All rights reserved.',1,'text','','设置在网站底部显示的版权信息,如“版权所有 © 2014-2015 科斯克网络科技”',1406991855,1406992583,8,1),(9,'网站备案号','WEB_SITE_ICP','苏ICP备1502009号',1,'text','','设置在网站底部显示的备案号,如“苏ICP备1502009号\"',1378900335,1415983236,9,1),(10,'站点统计','WEB_SITE_STATISTICS','',1,'textarea','','支持百度、Google、cnzz等所有Javascript的统计代码',1378900335,1415983236,10,1),(11,'文件上传大小','UPLOAD_FILE_SIZE','10',2,'num','','文件上传大小单位:MB',1428681031,1428681031,1,1),(12,'图片上传大小','UPLOAD_IMAGE_SIZE','2',2,'num','','图片上传大小单位:MB',1428681071,1428681071,2,1),(13,'后台多标签','ADMIN_TABS','0',2,'radio','0:关闭\r\n1:开启','',1453445526,1453445526,3,1),(14,'分页数量','ADMIN_PAGE_ROWS','10',2,'num','','分页时每页的记录数',1434019462,1434019481,4,1),(15,'后台主题','ADMIN_THEME','admin',2,'select','admin:默认主题\r\naliyun:阿里云风格','后台界面主题',1436678171,1436690570,5,1),(16,'导航分组','NAV_GROUP_LIST','top:顶部导航\r\nmain:主导航\r\nbottom:底部导航',2,'array','','导航分组',1458382037,1458382061,6,1),(17,'配置分组','CONFIG_GROUP_LIST','1:基本\r\n2:系统\r\n3:开发\r\n4:部署',2,'array','','配置分组',1379228036,1426930700,7,1),(18,'开发模式','DEVELOP_MODE','1',3,'select','1:开启\r\n0:关闭','开发模式下会显示菜单管理、配置管理、数据字典等开发者工具',1432393583,1432393583,1,1),(19,'是否显示页面Trace','SHOW_PAGE_TRACE','0',3,'select','0:关闭\r\n1:开启','是否显示页面Trace信息',1387165685,1387165685,2,1),(20,'系统加密KEY','AUTH_KEY','jqs%]Xx*.zdOEpJ`hp\"p;[uH*WgX@Ri(.N=IAW(:;~bmK(#KM>F?rKK|NOa+tkxW',3,'textarea','','轻易不要修改此项,否则容易造成用户无法登录;如要修改,务必备份原key',1438647773,1438647815,3,1),(21,'URL模式','URL_MODEL','3',4,'select','1:PATHINFO模式\r\n2:REWRITE模式\r\n3:兼容模式','',1438423248,1438423248,1,1);
+
+#
+# Structure for table "ly_admin_group"
+#
+
+DROP TABLE IF EXISTS `ly_admin_group`;
+CREATE TABLE `ly_admin_group` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '部门ID',
+ `pid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '上级部门ID',
+ `title` varchar(31) NOT NULL DEFAULT '' COMMENT '部门名称',
+ `icon` varchar(31) NOT NULL DEFAULT '' COMMENT '图标',
+ `menu_auth` text NOT NULL COMMENT '权限列表',
+ `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
+ `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '修改时间',
+ `sort` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '排序(同级有效)',
+ `status` tinyint(3) NOT NULL DEFAULT '0' COMMENT '状态',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='部门信息表';
+
+#
+# Data for table "ly_admin_group"
+#
+
+INSERT INTO `ly_admin_group` VALUES (1,0,'超级管理员','','',1426881003,1427552428,0,1);
+
+#
+# Structure for table "ly_admin_hook"
+#
+
+DROP TABLE IF EXISTS `ly_admin_hook`;
+CREATE TABLE `ly_admin_hook` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '钩子ID',
+ `name` varchar(32) DEFAULT NULL COMMENT '钩子名称',
+ `description` text NOT NULL COMMENT '描述',
+ `addons` varchar(255) NOT NULL DEFAULT '' COMMENT '钩子挂载的插件',
+ `type` tinyint(4) unsigned NOT NULL DEFAULT '1' COMMENT '类型',
+ `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
+ `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
+ `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COMMENT='钩子表';
+
+#
+# Data for table "ly_admin_hook"
+#
+
+INSERT INTO `ly_admin_hook` VALUES (1,'AdminIndex','后台首页小工具','后台首页小工具',1,1446522155,1446522155,1),(2,'FormBuilderExtend','FormBuilder类型扩展Builder','',1,1447831268,1447831268,1),(3,'UploadFile','上传文件钩子','',1,1407681961,1407681961,1),(4,'PageHeader','页面header钩子,一般用于加载插件CSS文件和代码','',1,1407681961,1407681961,1),(5,'PageFooter','页面footer钩子,一般用于加载插件CSS文件和代码','RocketToTop',1,1407681961,1407681961,1),(6,'CommonHook','通用钩子,自定义用途,一般用来定制特殊功能','',1,1456147822,1456147822,1);
+
+#
+# Structure for table "ly_admin_module"
+#
+
+DROP TABLE IF EXISTS `ly_admin_module`;
+CREATE TABLE `ly_admin_module` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `name` varchar(31) DEFAULT NULL COMMENT '名称',
+ `title` varchar(63) NOT NULL DEFAULT '' COMMENT '标题',
+ `logo` varchar(63) NOT NULL DEFAULT '' COMMENT '图片图标',
+ `icon` varchar(31) NOT NULL DEFAULT '' COMMENT '字体图标',
+ `icon_color` varchar(7) NOT NULL DEFAULT '' COMMENT '字体图标颜色',
+ `description` varchar(127) NOT NULL DEFAULT '' COMMENT '描述',
+ `developer` varchar(31) NOT NULL DEFAULT '' COMMENT '开发者',
+ `version` varchar(7) NOT NULL DEFAULT '' COMMENT '版本',
+ `user_nav` text NOT NULL COMMENT '个人中心导航',
+ `config` text NOT NULL COMMENT '配置',
+ `admin_menu` text NOT NULL COMMENT '菜单节点',
+ `is_system` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否允许卸载',
+ `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
+ `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
+ `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
+ `status` tinyint(3) NOT NULL DEFAULT '0' COMMENT '状态',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='模块功能表';
+
+#
+# Data for table "ly_admin_module"
+#
+
+INSERT INTO `ly_admin_module` VALUES (1,'Admin','系统','','fa fa-cog','#3CA6F1','核心系统','南京科斯克网络科技有限公司','1.0.0','','','{\"1\":{\"pid\":\"0\",\"title\":\"\\u7cfb\\u7edf\",\"icon\":\"fa fa-cog\",\"level\":\"system\",\"id\":\"1\"},\"2\":{\"pid\":\"1\",\"title\":\"\\u7cfb\\u7edf\\u529f\\u80fd\",\"icon\":\"fa fa-folder-open-o\",\"id\":\"2\"},\"3\":{\"pid\":\"2\",\"title\":\"\\u7cfb\\u7edf\\u8bbe\\u7f6e\",\"icon\":\"fa fa-wrench\",\"url\":\"Admin\\/Config\\/group\",\"id\":\"3\"},\"4\":{\"pid\":\"3\",\"title\":\"\\u4fee\\u6539\\u8bbe\\u7f6e\",\"url\":\"Admin\\/Config\\/groupSave\",\"id\":\"4\"},\"5\":{\"pid\":\"2\",\"title\":\"\\u5bfc\\u822a\\u7ba1\\u7406\",\"icon\":\"fa fa-map-signs\",\"url\":\"Admin\\/Nav\\/index\",\"id\":\"5\"},\"6\":{\"pid\":\"5\",\"title\":\"\\u65b0\\u589e\",\"url\":\"Admin\\/Nav\\/add\",\"id\":\"6\"},\"7\":{\"pid\":\"5\",\"title\":\"\\u7f16\\u8f91\",\"url\":\"Admin\\/Nav\\/edit\",\"id\":\"7\"},\"8\":{\"pid\":\"5\",\"title\":\"\\u8bbe\\u7f6e\\u72b6\\u6001\",\"url\":\"Admin\\/Nav\\/setStatus\",\"id\":\"8\"},\"13\":{\"pid\":\"2\",\"title\":\"\\u914d\\u7f6e\\u7ba1\\u7406\",\"icon\":\"fa fa-cogs\",\"url\":\"Admin\\/Config\\/index\",\"id\":\"13\"},\"14\":{\"pid\":\"13\",\"title\":\"\\u65b0\\u589e\",\"url\":\"Admin\\/Config\\/add\",\"id\":\"14\"},\"15\":{\"pid\":\"13\",\"title\":\"\\u7f16\\u8f91\",\"url\":\"Admin\\/Config\\/edit\",\"id\":\"15\"},\"16\":{\"pid\":\"13\",\"title\":\"\\u8bbe\\u7f6e\\u72b6\\u6001\",\"url\":\"Admin\\/Config\\/setStatus\",\"id\":\"16\"},\"17\":{\"pid\":\"2\",\"title\":\"\\u4e0a\\u4f20\\u7ba1\\u7406\",\"icon\":\"fa fa-upload\",\"url\":\"Admin\\/Upload\\/index\",\"id\":\"17\"},\"18\":{\"pid\":\"17\",\"title\":\"\\u4e0a\\u4f20\\u6587\\u4ef6\",\"url\":\"Admin\\/Upload\\/upload\",\"id\":\"18\"},\"19\":{\"pid\":\"17\",\"title\":\"\\u5220\\u9664\\u6587\\u4ef6\",\"url\":\"Admin\\/Upload\\/delete\",\"id\":\"19\"},\"20\":{\"pid\":\"17\",\"title\":\"\\u8bbe\\u7f6e\\u72b6\\u6001\",\"url\":\"Admin\\/Upload\\/setStatus\",\"id\":\"20\"},\"21\":{\"pid\":\"17\",\"title\":\"\\u4e0b\\u8f7d\\u8fdc\\u7a0b\\u56fe\\u7247\",\"url\":\"Admin\\/Upload\\/downremoteimg\",\"id\":\"21\"},\"22\":{\"pid\":\"17\",\"title\":\"\\u6587\\u4ef6\\u6d4f\\u89c8\",\"url\":\"Admin\\/Upload\\/fileManager\",\"id\":\"22\"},\"23\":{\"pid\":\"1\",\"title\":\"\\u7cfb\\u7edf\\u6743\\u9650\",\"icon\":\"fa fa-folder-open-o\",\"id\":\"23\"},\"24\":{\"pid\":\"23\",\"title\":\"\\u7528\\u6237\\u7ba1\\u7406\",\"icon\":\"fa fa-user\",\"url\":\"Admin\\/User\\/index\",\"id\":\"24\"},\"25\":{\"pid\":\"24\",\"title\":\"\\u65b0\\u589e\",\"url\":\"Admin\\/User\\/add\",\"id\":\"25\"},\"26\":{\"pid\":\"24\",\"title\":\"\\u7f16\\u8f91\",\"url\":\"Admin\\/User\\/edit\",\"id\":\"26\"},\"27\":{\"pid\":\"24\",\"title\":\"\\u8bbe\\u7f6e\\u72b6\\u6001\",\"url\":\"Admin\\/User\\/setStatus\",\"id\":\"27\"},\"28\":{\"pid\":\"23\",\"title\":\"\\u7ba1\\u7406\\u5458\\u7ba1\\u7406\",\"icon\":\"fa fa-lock\",\"url\":\"Admin\\/Access\\/index\",\"id\":\"28\"},\"29\":{\"pid\":\"28\",\"title\":\"\\u65b0\\u589e\",\"url\":\"Admin\\/Access\\/add\",\"id\":\"29\"},\"30\":{\"pid\":\"28\",\"title\":\"\\u7f16\\u8f91\",\"url\":\"Admin\\/Access\\/edit\",\"id\":\"30\"},\"31\":{\"pid\":\"28\",\"title\":\"\\u8bbe\\u7f6e\\u72b6\\u6001\",\"url\":\"Admin\\/Access\\/setStatus\",\"id\":\"31\"},\"32\":{\"pid\":\"23\",\"title\":\"\\u7528\\u6237\\u7ec4\\u7ba1\\u7406\",\"icon\":\"fa fa-sitemap\",\"url\":\"Admin\\/Group\\/index\",\"id\":\"32\"},\"33\":{\"pid\":\"32\",\"title\":\"\\u65b0\\u589e\",\"url\":\"Admin\\/Group\\/add\",\"id\":\"33\"},\"34\":{\"pid\":\"32\",\"title\":\"\\u7f16\\u8f91\",\"url\":\"Admin\\/Group\\/edit\",\"id\":\"34\"},\"35\":{\"pid\":\"32\",\"title\":\"\\u8bbe\\u7f6e\\u72b6\\u6001\",\"url\":\"Admin\\/Group\\/setStatus\",\"id\":\"35\"},\"36\":{\"pid\":\"1\",\"title\":\"\\u6269\\u5c55\\u4e2d\\u5fc3\",\"icon\":\"fa fa-folder-open-o\",\"id\":\"36\"},\"44\":{\"pid\":\"36\",\"title\":\"\\u529f\\u80fd\\u6a21\\u5757\",\"icon\":\"fa fa-th-large\",\"url\":\"Admin\\/Module\\/index\",\"id\":\"44\"},\"45\":{\"pid\":\"44\",\"title\":\"\\u5b89\\u88c5\",\"url\":\"Admin\\/Module\\/install\",\"id\":\"45\"},\"46\":{\"pid\":\"44\",\"title\":\"\\u5378\\u8f7d\",\"url\":\"Admin\\/Module\\/uninstall\",\"id\":\"46\"},\"47\":{\"pid\":\"44\",\"title\":\"\\u66f4\\u65b0\\u4fe1\\u606f\",\"url\":\"Admin\\/Module\\/updateInfo\",\"id\":\"47\"},\"48\":{\"pid\":\"44\",\"title\":\"\\u8bbe\\u7f6e\\u72b6\\u6001\",\"url\":\"Admin\\/Module\\/setStatus\",\"id\":\"48\"},\"49\":{\"pid\":\"36\",\"title\":\"\\u63d2\\u4ef6\\u7ba1\\u7406\",\"icon\":\"fa fa-th\",\"url\":\"Admin\\/Addon\\/index\",\"id\":\"49\"},\"50\":{\"pid\":\"49\",\"title\":\"\\u5b89\\u88c5\",\"url\":\"Admin\\/Addon\\/install\",\"id\":\"50\"},\"51\":{\"pid\":\"49\",\"title\":\"\\u5378\\u8f7d\",\"url\":\"Admin\\/Addon\\/uninstall\",\"id\":\"51\"},\"52\":{\"pid\":\"49\",\"title\":\"\\u8fd0\\u884c\",\"url\":\"Admin\\/Addon\\/execute\",\"id\":\"52\"},\"53\":{\"pid\":\"49\",\"title\":\"\\u8bbe\\u7f6e\",\"url\":\"Admin\\/Addon\\/config\",\"id\":\"53\"},\"54\":{\"pid\":\"49\",\"title\":\"\\u540e\\u53f0\\u7ba1\\u7406\",\"url\":\"Admin\\/Addon\\/adminList\",\"id\":\"54\"},\"55\":{\"pid\":\"54\",\"title\":\"\\u65b0\\u589e\\u6570\\u636e\",\"url\":\"Admin\\/Addon\\/adminAdd\",\"id\":\"55\"},\"56\":{\"pid\":\"54\",\"title\":\"\\u7f16\\u8f91\\u6570\\u636e\",\"url\":\"Admin\\/Addon\\/adminEdit\",\"id\":\"56\"},\"57\":{\"pid\":\"54\",\"title\":\"\\u8bbe\\u7f6e\\u72b6\\u6001\",\"url\":\"Admin\\/Addon\\/setStatus\",\"id\":\"57\"}}',1,1438651748,1476718511,0,1);
+
+#
+# Structure for table "ly_admin_nav"
+#
+
+DROP TABLE IF EXISTS `ly_admin_nav`;
+CREATE TABLE `ly_admin_nav` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `group` varchar(11) NOT NULL DEFAULT '' COMMENT '分组',
+ `pid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '父ID',
+ `title` varchar(31) NOT NULL DEFAULT '' COMMENT '导航标题',
+ `type` varchar(15) NOT NULL DEFAULT '' COMMENT '导航类型',
+ `value` text COMMENT '导航值',
+ `target` varchar(11) NOT NULL DEFAULT '' COMMENT '打开方式',
+ `icon` varchar(32) NOT NULL DEFAULT '' COMMENT '图标',
+ `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
+ `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '修改时间',
+ `sort` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
+ `status` tinyint(3) NOT NULL DEFAULT '0' COMMENT '状态',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8 COMMENT='前台导航表';
+
+#
+# Data for table "ly_admin_nav"
+#
+
+INSERT INTO `ly_admin_nav` VALUES (1,'bottom',0,'关于','link','','','',1449742225,1449742255,0,1),(2,'bottom',1,'关于我们','page','','','',1449742312,1449742312,0,1),(4,'bottom',1,'服务产品','page','','','',1449742597,1449742651,0,1),(5,'bottom',1,'商务合作','page','','','',1449742664,1449742664,0,1),(6,'bottom',1,'加入我们','page','','','',1449742678,1449742697,0,1),(7,'bottom',0,'帮助','page','','','',1449742688,1449742688,0,1),(8,'bottom',7,'用户协议','page','','','',1449742706,1449742706,0,1),(9,'bottom',7,'意见反馈','page','','','',1449742716,1449742716,0,1),(10,'bottom',7,'常见问题','page','','','',1449742728,1449742728,0,1),(11,'bottom',0,'联系方式','page','','','',1449742742,1449742742,0,1),(12,'bottom',11,'联系我们','page','','','',1449742752,1449742752,0,1),(13,'bottom',11,'新浪微博','page','','','',1449742802,1449742802,0,1),(14,'main',0,'首页','link','','','',1457084559,1472993801,0,1),(15,'main',0,'产品中心','page','','','',1457084559,1457084559,0,1),(16,'main',0,'客户服务','page','','','',1457084572,1457084572,0,1),(17,'main',0,'案例展示','page','','','',1457084583,1457084583,0,1),(18,'main',0,'新闻动态','page','','','',1457084714,1457084714,0,1),(19,'main',0,'联系我们','page','','','',1457084725,1457084725,0,1);
+
+#
+# Structure for table "ly_admin_post"
+#
+
+DROP TABLE IF EXISTS `ly_admin_post`;
+CREATE TABLE `ly_admin_post` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `cid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分类ID',
+ `title` varchar(127) NOT NULL DEFAULT '' COMMENT '标题',
+ `cover` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '封面',
+ `abstract` varchar(255) DEFAULT '' COMMENT '摘要',
+ `content` text COMMENT '内容',
+ `view_count` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '阅读',
+ `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
+ `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '修改时间',
+ `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
+ `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '状态',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文章列表';
+
+#
+# Data for table "ly_admin_post"
+#
+
+
+#
+# Structure for table "ly_admin_upload"
+#
+
+DROP TABLE IF EXISTS `ly_admin_upload`;
+CREATE TABLE `ly_admin_upload` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `uid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT 'UID',
+ `name` varchar(255) NOT NULL DEFAULT '' COMMENT '文件名',
+ `path` varchar(255) NOT NULL DEFAULT '' COMMENT '文件路径',
+ `url` varchar(255) DEFAULT NULL COMMENT '文件链接',
+ `ext` char(4) NOT NULL DEFAULT '' COMMENT '文件类型',
+ `size` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '文件大小',
+ `md5` char(32) DEFAULT NULL COMMENT '文件md5',
+ `sha1` char(40) DEFAULT NULL COMMENT '文件sha1编码',
+ `location` varchar(15) NOT NULL DEFAULT '' COMMENT '文件存储位置',
+ `download` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '下载次数',
+ `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '上传时间',
+ `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '修改时间',
+ `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
+ `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `url` (`url`),
+ UNIQUE KEY `md5` (`md5`),
+ UNIQUE KEY `sha1` (`sha1`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文件上传表';
+
+#
+# Data for table "ly_admin_upload"
+#
+
+
+#
+# Structure for table "ly_admin_user"
+#
+
+DROP TABLE IF EXISTS `ly_admin_user`;
+CREATE TABLE `ly_admin_user` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'UID',
+ `user_type` int(11) NOT NULL DEFAULT '1' COMMENT '用户类型',
+ `nickname` varchar(63) DEFAULT NULL COMMENT '昵称',
+ `username` varchar(31) NOT NULL DEFAULT '' COMMENT '用户名',
+ `password` varchar(63) NOT NULL DEFAULT '' COMMENT '密码',
+ `email` varchar(63) NOT NULL DEFAULT '' COMMENT '邮箱',
+ `email_bind` tinyint(1) NOT NULL DEFAULT '0' COMMENT '邮箱验证',
+ `mobile` varchar(11) NOT NULL DEFAULT '' COMMENT '手机号',
+ `mobile_bind` tinyint(1) NOT NULL DEFAULT '0' COMMENT '邮箱验证',
+ `avatar` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '头像',
+ `score` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '积分',
+ `money` decimal(11,2) NOT NULL DEFAULT '0.00' COMMENT '余额',
+ `reg_ip` bigint(20) NOT NULL DEFAULT '0' COMMENT '注册IP',
+ `reg_type` varchar(15) NOT NULL DEFAULT '' COMMENT '注册方式',
+ `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
+ `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
+ `status` tinyint(3) NOT NULL DEFAULT '0' COMMENT '状态',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `username` (`username`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='用户账号表';
+
+#
+# Data for table "ly_admin_user"
+#
+
+INSERT INTO `ly_admin_user` VALUES (1,1,'超级管理员','admin','3a123a2a57235e37bd8df4ab88d99d98','',0,'',0,0,0,0.00,0,'',1438651748,1438651748,1);
diff --git a/Application/Install/View/Index/complete.html b/Application/Install/View/Index/complete.html
new file mode 100644
index 00000000..11e30e5a
--- /dev/null
+++ b/Application/Install/View/Index/complete.html
@@ -0,0 +1,32 @@
+
+
+
+ 安装协议
+ 环境检测
+ 参数设置
+ 开始安装
+ 完成安装
+
+
+
+
+
+
+ 恭喜您,{:C('INSTALL_PRODUCT_NAME')}安装完成!
+
+
+
+
SUCCESS !
+
+
+ 后台管理员用户名密码默认均为admin,请尽快登录系统进行修改,防止系统被恶意攻击。
+
+
登录后台
+
访问首页
+
+
+
+
+
diff --git a/Application/Install/View/Index/step1.html b/Application/Install/View/Index/step1.html
new file mode 100644
index 00000000..3603e2ed
--- /dev/null
+++ b/Application/Install/View/Index/step1.html
@@ -0,0 +1,31 @@
+
+
+
+ 安装协议
+ 环境检测
+ 参数设置
+ 开始安装
+ 完成安装
+
+
+
+
+
+
+ {:C('INSTALL_PRODUCT_NAME')} 安装协议
+
+
+
感谢您选择{:C('INSTALL_PRODUCT_NAME')},希望我们的努力能为您提供一个简单、高效、卓越的轻量级通用后台。
+
用户须知:本协议是您与{:C('INSTALL_COMPANY_NAME')}之间关于您使用{:C('INSTALL_PRODUCT_NAME')}产品及服务的法律协议。无论您是个人或组织、盈利与否、用途如何(包括以学习和研究为目的),均需仔细阅读本协议,包括免除或者限制{:C('INSTALL_COMPANY_NAME')}责任的免责条款及对您的权利限制。请您审阅并接受或不接受本服务条款。如您不同意本服务条款及{:C('INSTALL_COMPANY_NAME')}随时对其的修改,您应不使用或主动取消{:C('INSTALL_PRODUCT_NAME')}产品。否则,您的任何对{:C('INSTALL_PRODUCT_NAME')}的相关服务的注册、登录、下载、查看等使用行为将被视为您对本服务条款全部的完全接受,包括接受{:C('INSTALL_COMPANY_NAME')}对服务条款随时所做的任何修改。
+
本服务条款一旦发生变更, {:C('INSTALL_COMPANY_NAME')}将在官网上公布修改内容。修改后的服务条款一旦在网站公布即有效代替原来的服务条款。您可随时登陆官网查阅最新版服务条款。如果您选择接受本条款,即表示您同意接受协议各项条件的约束。如果您不同意本服务条款,则不能获得使用本服务的权利。您若有违反本条款规定,{:C('INSTALL_COMPANY_NAME')}有权随时中止或终止您对{:C('INSTALL_PRODUCT_NAME')}产品的使用资格并保留追究相关法律责任的权利。
+
在理解、同意、并遵守本协议的全部条款后,方可开始使用{:C('INSTALL_PRODUCT_NAME')}产品。您也可能与{:C('INSTALL_COMPANY_NAME')}直接签订另一书面协议,以补充或者取代本协议的全部或者任何部分。
+
{:C('INSTALL_COMPANY_NAME')}拥有{:C('INSTALL_PRODUCT_NAME')}的全部知识产权,包括商标和著作权。{:C('INSTALL_COMPANY_NAME')}只允许您在遵守本协议各项条款的情况下复制、下载、安装、使用或者以其他方式受益于本软件的功能或者知识产权。
+
同意安装协议
+
不同意
+
+
+
+
+
diff --git a/Application/Install/View/Index/step2.html b/Application/Install/View/Index/step2.html
new file mode 100644
index 00000000..c28fe4c7
--- /dev/null
+++ b/Application/Install/View/Index/step2.html
@@ -0,0 +1,83 @@
+
+
+
+ 安装协议
+ 环境检测
+ 参数设置
+ 开始安装
+ 完成安装
+
+
+
+
+
+
+ 环境检测
+
+
+
+ 运行环境检查
+
+
+ 项目
+ 所需配置
+ 当前配置
+
+
+
+
+
+ {$item['title']}
+ {$item['limit']}
+ {$item['current']}
+
+
+
+
+
+
+ 目录、文件权限检查
+
+
+ 目录/文件
+ 所需状态
+ 当前状态
+
+
+
+
+
+ {$item['path']}
+ 可写
+ {$item['title']}
+
+
+
+
+
+
+ 函数及扩展依赖性检查
+
+
+ 名称
+ 检查结果
+
+
+
+
+
+ {$item['name']}
+ {$item['title']}
+
+
+
+
+
下一步
+
上一步
+
+
+
+
+
diff --git a/Application/Install/View/Index/step3.html b/Application/Install/View/Index/step3.html
new file mode 100644
index 00000000..377ce469
--- /dev/null
+++ b/Application/Install/View/Index/step3.html
@@ -0,0 +1,78 @@
+
+
+
+ 安装协议
+ 环境检测
+ 参数设置
+ 开始安装
+ 完成安装
+
+
+
+
+
diff --git a/Application/Install/View/Index/step4.html b/Application/Install/View/Index/step4.html
new file mode 100644
index 00000000..70a2201c
--- /dev/null
+++ b/Application/Install/View/Index/step4.html
@@ -0,0 +1,37 @@
+
+
+
+ 安装协议
+ 环境检测
+ 参数设置
+ 开始安装
+ 完成安装
+
+
+
+
+
diff --git a/Application/Install/View/Public/layout.html b/Application/Install/View/Public/layout.html
new file mode 100644
index 00000000..b8e082fe
--- /dev/null
+++ b/Application/Install/View/Public/layout.html
@@ -0,0 +1,42 @@
+
+
+
+
+ 安装{:C('INSTALL_PRODUCT_NAME')}-{$meta_title}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Data/README.md b/Data/README.md
new file mode 100644
index 00000000..7542ea40
--- /dev/null
+++ b/Data/README.md
@@ -0,0 +1 @@
+# 数据目录
diff --git a/Framework/Common/functions.php b/Framework/Common/functions.php
new file mode 100644
index 00000000..6e09e173
--- /dev/null
+++ b/Framework/Common/functions.php
@@ -0,0 +1,1775 @@
+
+// +----------------------------------------------------------------------
+
+/**
+ * Think 系统函数库
+ */
+
+/**
+ * 获取和设置配置参数 支持批量定义
+ * @param string|array $name 配置变量
+ * @param mixed $value 配置值
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+function C($name = null, $value = null, $default = null)
+{
+ static $_config = array();
+ // 无参数时获取所有
+ if (empty($name)) {
+ return $_config;
+ }
+ // 优先执行设置获取或赋值
+ if (is_string($name)) {
+ if (!strpos($name, '.')) {
+ $name = strtoupper($name);
+ if (is_null($value)) {
+ return isset($_config[$name]) ? $_config[$name] : $default;
+ }
+
+ $_config[$name] = $value;
+ return null;
+ }
+ // 二维数组设置和获取支持
+ $name = explode('.', $name);
+ $name[0] = strtoupper($name[0]);
+ if (is_null($value)) {
+ return isset($_config[$name[0]][$name[1]]) ? $_config[$name[0]][$name[1]] : $default;
+ }
+
+ $_config[$name[0]][$name[1]] = $value;
+ return null;
+ }
+ // 批量设置
+ if (is_array($name)) {
+ $_config = array_merge($_config, array_change_key_case($name, CASE_UPPER));
+ return null;
+ }
+ return null; // 避免非法参数
+}
+
+/**
+ * 加载配置文件 支持格式转换 仅支持一级配置
+ * @param string $file 配置文件名
+ * @param string $parse 配置解析方法 有些格式需要用户自己解析
+ * @return array
+ */
+function load_config($file, $parse = CONF_PARSE)
+{
+ $ext = pathinfo($file, PATHINFO_EXTENSION);
+ switch ($ext) {
+ case 'php':
+ return include $file;
+ case 'ini':
+ return parse_ini_file($file);
+ case 'yaml':
+ return yaml_parse_file($file);
+ case 'xml':
+ return (array) simplexml_load_file($file);
+ case 'json':
+ return json_decode(file_get_contents($file), true);
+ default:
+ if (function_exists($parse)) {
+ return $parse($file);
+ } else {
+ E(L('_NOT_SUPPORT_') . ':' . $ext);
+ }
+ }
+}
+
+/**
+ * 解析yaml文件返回一个数组
+ * @param string $file 配置文件名
+ * @return array
+ */
+if (!function_exists('yaml_parse_file')) {
+ function yaml_parse_file($file)
+ {
+ vendor('spyc.Spyc');
+ return Spyc::YAMLLoad($file);
+ }
+}
+
+/**
+ * 抛出异常处理
+ * @param string $msg 异常消息
+ * @param integer $code 异常代码 默认为0
+ * @throws Think\Exception
+ * @return void
+ */
+function E($msg, $code = 0)
+{
+ throw new Think\Exception($msg, $code);
+}
+
+/**
+ * 记录和统计时间(微秒)和内存使用情况
+ * 使用方法:
+ *
+ * G('begin'); // 记录开始标记位
+ * // ... 区间运行代码
+ * G('end'); // 记录结束标签位
+ * echo G('begin','end',6); // 统计区间运行时间 精确到小数后6位
+ * echo G('begin','end','m'); // 统计区间内存使用情况
+ * 如果end标记位没有定义,则会自动以当前作为标记位
+ * 其中统计内存使用需要 MEMORY_LIMIT_ON 常量为true才有效
+ *
+ * @param string $start 开始标签
+ * @param string $end 结束标签
+ * @param integer|string $dec 小数位或者m
+ * @return mixed
+ */
+function G($start, $end = '', $dec = 4)
+{
+ static $_info = array();
+ static $_mem = array();
+ if (is_float($end)) {
+ // 记录时间
+ $_info[$start] = $end;
+ } elseif (!empty($end)) {
+ // 统计时间和内存使用
+ if (!isset($_info[$end])) {
+ $_info[$end] = microtime(true);
+ }
+
+ if (MEMORY_LIMIT_ON && 'm' == $dec) {
+ if (!isset($_mem[$end])) {
+ $_mem[$end] = memory_get_usage();
+ }
+
+ return number_format(($_mem[$end] - $_mem[$start]) / 1024);
+ } else {
+ return number_format(($_info[$end] - $_info[$start]), $dec);
+ }
+
+ } else {
+ // 记录时间和内存使用
+ $_info[$start] = microtime(true);
+ if (MEMORY_LIMIT_ON) {
+ $_mem[$start] = memory_get_usage();
+ }
+
+ }
+ return null;
+}
+
+/**
+ * 获取和设置语言定义(不区分大小写)
+ * @param string|array $name 语言变量
+ * @param mixed $value 语言值或者变量
+ * @return mixed
+ */
+function L($name = null, $value = null)
+{
+ static $_lang = array();
+ // 空参数返回所有定义
+ if (empty($name)) {
+ return $_lang;
+ }
+
+ // 判断语言获取(或设置)
+ // 若不存在,直接返回全大写$name
+ if (is_string($name)) {
+ $name = strtoupper($name);
+ if (is_null($value)) {
+ return isset($_lang[$name]) ? $_lang[$name] : $name;
+ } elseif (is_array($value)) {
+ // 支持变量
+ $replace = array_keys($value);
+ foreach ($replace as &$v) {
+ $v = '{$' . $v . '}';
+ }
+ return str_replace($replace, $value, isset($_lang[$name]) ? $_lang[$name] : $name);
+ }
+ $_lang[$name] = $value; // 语言定义
+ return null;
+ }
+ // 批量定义
+ if (is_array($name)) {
+ $_lang = array_merge($_lang, array_change_key_case($name, CASE_UPPER));
+ }
+
+ return null;
+}
+
+/**
+ * 添加和获取页面Trace记录
+ * @param string $value 变量
+ * @param string $label 标签
+ * @param string $level 日志级别
+ * @param boolean $record 是否记录日志
+ * @return void|array
+ */
+function trace($value = '[think]', $label = '', $level = 'DEBUG', $record = false)
+{
+ return Think\Think::trace($value, $label, $level, $record);
+}
+
+/**
+ * 编译文件
+ * @param string $filename 文件名
+ * @return string
+ */
+function compile($filename)
+{
+ $content = php_strip_whitespace($filename);
+ $content = trim(substr($content, 5));
+ // 替换预编译指令
+ $content = preg_replace('/\/\/\[RUNTIME\](.*?)\/\/\[\/RUNTIME\]/s', '', $content);
+ if (0 === strpos($content, 'namespace')) {
+ $content = preg_replace('/namespace\s(.*?);/', 'namespace \\1{', $content, 1);
+ } else {
+ $content = 'namespace {' . $content;
+ }
+ if ('?>' == substr($content, -2)) {
+ $content = substr($content, 0, -2);
+ }
+
+ return $content . '}';
+}
+
+/**
+ * 获取模版文件 格式 资源://模块@主题/控制器/操作
+ * @param string $template 模版资源地址
+ * @param string $layer 视图层(目录)名称
+ * @return string
+ */
+function T($template = '', $layer = '')
+{
+
+ // 解析模版资源地址
+ if (false === strpos($template, '://')) {
+ $template = 'http://' . str_replace(':', '/', $template);
+ }
+ $info = parse_url($template);
+ $file = $info['host'] . (isset($info['path']) ? $info['path'] : '');
+ $module = isset($info['user']) ? $info['user'] . '/' : MODULE_NAME . '/';
+ $extend = $info['scheme'];
+ $layer = $layer ? $layer : C('DEFAULT_V_LAYER');
+
+ // 获取当前主题的模版路径
+ $auto = C('AUTOLOAD_NAMESPACE');
+ if ($auto && isset($auto[$extend])) {
+ // 扩展资源
+ $baseUrl = $auto[$extend] . $module . $layer . '/';
+ } elseif (C('VIEW_PATH')) {
+ // 改变模块视图目录
+ $baseUrl = C('VIEW_PATH');
+ } elseif (defined('TMPL_PATH')) {
+ // 指定全局视图目录
+ $baseUrl = TMPL_PATH . $module;
+ } else {
+ $baseUrl = APP_PATH . $module . $layer . '/';
+ }
+
+ // 获取主题
+ $theme = substr_count($file, '/') < 2 ? C('DEFAULT_THEME') : '';
+
+ // 分析模板文件规则
+ $depr = C('TMPL_FILE_DEPR');
+ if ('' == $file) {
+ // 如果模板文件名为空 按照默认规则定位
+ $file = CONTROLLER_NAME . $depr . ACTION_NAME;
+ } elseif (false === strpos($file, '/')) {
+ $file = CONTROLLER_NAME . $depr . $file;
+ } elseif ('/' != $depr) {
+ $file = substr_count($file, '/') > 1 ? substr_replace($file, $depr, strrpos($file, '/'), 1) : str_replace('/', $depr, $file);
+ }
+ return $baseUrl . ($theme ? $theme . '/' : '') . $file . C('TMPL_TEMPLATE_SUFFIX');
+}
+
+/**
+ * 获取输入参数 支持过滤和默认值
+ * 使用方法:
+ *
+ * I('id',0); 获取id参数 自动判断get或者post
+ * I('post.name','','htmlspecialchars'); 获取$_POST['name']
+ * I('get.'); 获取$_GET
+ *
+ * @param string $name 变量的名称 支持指定类型
+ * @param mixed $default 不存在的时候默认值
+ * @param mixed $filter 参数过滤方法
+ * @param mixed $datas 要获取的额外数据源
+ * @return mixed
+ */
+function I($name, $default = '', $filter = null, $datas = null)
+{
+ static $_PUT = null;
+ if (strpos($name, '/')) {
+ // 指定修饰符
+ list($name, $type) = explode('/', $name, 2);
+ } elseif (C('VAR_AUTO_STRING')) {
+ // 默认强制转换为字符串
+ $type = 's';
+ }
+ if (strpos($name, '.')) {
+ // 指定参数来源
+ list($method, $name) = explode('.', $name, 2);
+ } else {
+ // 默认为自动判断
+ $method = 'param';
+ }
+ switch (strtolower($method)) {
+ case 'get':
+ $input = &$_GET;
+ break;
+ case 'post':
+ $input = &$_POST;
+ break;
+ case 'put':
+ if (is_null($_PUT)) {
+ parse_str(file_get_contents('php://input'), $_PUT);
+ }
+ $input = $_PUT;
+ break;
+ case 'param':
+ switch ($_SERVER['REQUEST_METHOD']) {
+ case 'POST':
+ $input = $_POST;
+ break;
+ case 'PUT':
+ if (is_null($_PUT)) {
+ parse_str(file_get_contents('php://input'), $_PUT);
+ }
+ $input = $_PUT;
+ break;
+ default:
+ $input = $_GET;
+ }
+ break;
+ case 'path':
+ $input = array();
+ if (!empty($_SERVER['PATH_INFO'])) {
+ $depr = C('URL_PATHINFO_DEPR');
+ $input = explode($depr, trim($_SERVER['PATH_INFO'], $depr));
+ }
+ break;
+ case 'request':
+ $input = &$_REQUEST;
+ break;
+ case 'session':
+ $input = &$_SESSION;
+ break;
+ case 'cookie':
+ $input = &$_COOKIE;
+ break;
+ case 'server':
+ $input = &$_SERVER;
+ break;
+ case 'globals':
+ $input = &$GLOBALS;
+ break;
+ case 'data':
+ $input = &$datas;
+ break;
+ default:
+ return null;
+ }
+ if ('' == $name) {
+ // 获取全部变量
+ $data = $input;
+ $filters = isset($filter) ? $filter : C('DEFAULT_FILTER');
+ if ($filters) {
+ if (is_string($filters)) {
+ $filters = explode(',', $filters);
+ }
+ foreach ($filters as $filter) {
+ $data = array_map_recursive($filter, $data); // 参数过滤
+ }
+ }
+ } elseif (isset($input[$name])) {
+ // 取值操作
+ $data = $input[$name];
+ $filters = isset($filter) ? $filter : C('DEFAULT_FILTER');
+ if ($filters) {
+ if (is_string($filters)) {
+ if (0 === strpos($filters, '/')) {
+ if (1 !== preg_match($filters, (string) $data)) {
+ // 支持正则验证
+ return isset($default) ? $default : null;
+ }
+ } else {
+ $filters = explode(',', $filters);
+ }
+ } elseif (is_int($filters)) {
+ $filters = array($filters);
+ }
+
+ if (is_array($filters)) {
+ foreach ($filters as $filter) {
+ $filter = trim($filter);
+ if (function_exists($filter)) {
+ $data = is_array($data) ? array_map_recursive($filter, $data) : $filter($data); // 参数过滤
+ } else {
+ $data = filter_var($data, is_int($filter) ? $filter : filter_id($filter));
+ if (false === $data) {
+ return isset($default) ? $default : null;
+ }
+ }
+ }
+ }
+ }
+ if (!empty($type)) {
+ switch (strtolower($type)) {
+ case 'a': // 数组
+ $data = (array) $data;
+ break;
+ case 'd': // 数字
+ $data = (int) $data;
+ break;
+ case 'f': // 浮点
+ $data = (float) $data;
+ break;
+ case 'b': // 布尔
+ $data = (boolean) $data;
+ break;
+ case 's':// 字符串
+ default:
+ $data = (string) $data;
+ }
+ }
+ } else {
+ // 变量默认值
+ $data = isset($default) ? $default : null;
+ }
+ is_array($data) && array_walk_recursive($data, 'think_filter');
+ return $data;
+}
+
+function array_map_recursive($filter, $data)
+{
+ $result = array();
+ foreach ($data as $key => $val) {
+ $result[$key] = is_array($val)
+ ? array_map_recursive($filter, $val)
+ : call_user_func($filter, $val);
+ }
+ return $result;
+}
+
+/**
+ * 设置和获取统计数据
+ * 使用方法:
+ *
+ * N('db',1); // 记录数据库操作次数
+ * N('read',1); // 记录读取次数
+ * echo N('db'); // 获取当前页面数据库的所有操作次数
+ * echo N('read'); // 获取当前页面读取次数
+ *
+ * @param string $key 标识位置
+ * @param integer $step 步进值
+ * @param boolean $save 是否保存结果
+ * @return mixed
+ */
+function N($key, $step = 0, $save = false)
+{
+ static $_num = array();
+ if (!isset($_num[$key])) {
+ $_num[$key] = (false !== $save) ? S('N_' . $key) : 0;
+ }
+ if (empty($step)) {
+ return $_num[$key];
+ } else {
+ $_num[$key] = $_num[$key] + (int) $step;
+ }
+ if (false !== $save) {
+ // 保存结果
+ S('N_' . $key, $_num[$key], $save);
+ }
+ return null;
+}
+
+/**
+ * 字符串命名风格转换
+ * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
+ * @param string $name 字符串
+ * @param integer $type 转换类型
+ * @return string
+ */
+function parse_name($name, $type = 0)
+{
+ if ($type) {
+ return ucfirst(preg_replace_callback('/_([a-zA-Z])/', function ($match) {return strtoupper($match[1]);}, $name));
+ } else {
+ return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
+ }
+}
+
+/**
+ * 优化的require_once
+ * @param string $filename 文件地址
+ * @return boolean
+ */
+function require_cache($filename)
+{
+ static $_importFiles = array();
+ if (!isset($_importFiles[$filename])) {
+ if (file_exists_case($filename)) {
+ require $filename;
+ $_importFiles[$filename] = true;
+ } else {
+ $_importFiles[$filename] = false;
+ }
+ }
+ return $_importFiles[$filename];
+}
+
+/**
+ * 区分大小写的文件存在判断
+ * @param string $filename 文件地址
+ * @return boolean
+ */
+function file_exists_case($filename)
+{
+ if (is_file($filename)) {
+ if (IS_WIN && APP_DEBUG) {
+ if (basename(realpath($filename)) != basename($filename)) {
+ return false;
+ }
+
+ }
+ return true;
+ }
+ return false;
+}
+
+/**
+ * 导入所需的类库 同java的Import 本函数有缓存功能
+ * @param string $class 类库命名空间字符串
+ * @param string $baseUrl 起始路径
+ * @param string $ext 导入的文件扩展名
+ * @return boolean
+ */
+function import($class, $baseUrl = '', $ext = EXT)
+{
+ static $_file = array();
+ $class = str_replace(array('.', '#'), array('/', '.'), $class);
+ if (isset($_file[$class . $baseUrl])) {
+ return true;
+ } else {
+ $_file[$class . $baseUrl] = true;
+ }
+
+ $class_strut = explode('/', $class);
+ if (empty($baseUrl)) {
+ if ('@' == $class_strut[0] || MODULE_NAME == $class_strut[0]) {
+ //加载当前模块的类库
+ $baseUrl = MODULE_PATH;
+ $class = substr_replace($class, '', 0, strlen($class_strut[0]) + 1);
+ } elseif ('Common' == $class_strut[0]) {
+ //加载公共模块的类库
+ $baseUrl = COMMON_PATH;
+ $class = substr($class, 7);
+ } elseif (in_array($class_strut[0], array('Think', 'Org', 'Behavior', 'Com', 'Vendor')) || is_dir(LIB_PATH . $class_strut[0])) {
+ // 系统类库包和第三方类库包
+ $baseUrl = LIB_PATH;
+ } else {
+ // 加载其他模块的类库
+ $baseUrl = APP_PATH;
+ }
+ }
+ if (substr($baseUrl, -1) != '/') {
+ $baseUrl .= '/';
+ }
+
+ $classfile = $baseUrl . $class . $ext;
+ if (!class_exists(basename($class), false)) {
+ // 如果类不存在 则导入类库文件
+ return require_cache($classfile);
+ }
+ return null;
+}
+
+/**
+ * 基于命名空间方式导入函数库
+ * load('@.Util.Array')
+ * @param string $name 函数库命名空间字符串
+ * @param string $baseUrl 起始路径
+ * @param string $ext 导入的文件扩展名
+ * @return void
+ */
+function load($name, $baseUrl = '', $ext = '.php')
+{
+ $name = str_replace(array('.', '#'), array('/', '.'), $name);
+ if (empty($baseUrl)) {
+ if (0 === strpos($name, '@/')) {
+ //加载当前模块函数库
+ $baseUrl = MODULE_PATH . 'Common/';
+ $name = substr($name, 2);
+ } else {
+ //加载其他模块函数库
+ $array = explode('/', $name);
+ $baseUrl = APP_PATH . array_shift($array) . '/Common/';
+ $name = implode('/', $array);
+ }
+ }
+ if (substr($baseUrl, -1) != '/') {
+ $baseUrl .= '/';
+ }
+
+ require_cache($baseUrl . $name . $ext);
+}
+
+/**
+ * 快速导入第三方框架类库 所有第三方框架的类库文件统一放到 系统的Vendor目录下面
+ * @param string $class 类库
+ * @param string $baseUrl 基础目录
+ * @param string $ext 类库后缀
+ * @return boolean
+ */
+function vendor($class, $baseUrl = '', $ext = '.php')
+{
+ if (empty($baseUrl)) {
+ $baseUrl = VENDOR_PATH;
+ }
+
+ return import($class, $baseUrl, $ext);
+}
+
+/**
+ * 实例化模型类 格式 [资源://][模块/]模型
+ * @param string $name 资源地址
+ * @param string $layer 模型层名称
+ * @return Think\Model
+ */
+function D($name = '', $layer = '')
+{
+ if (empty($name)) {
+ return new Think\Model;
+ }
+
+ static $_model = array();
+ $layer = $layer ?: C('DEFAULT_M_LAYER');
+ if (isset($_model[$name . $layer])) {
+ return $_model[$name . $layer];
+ }
+
+ $class = parse_res_name($name, $layer);
+ if (class_exists($class)) {
+ $model = new $class(basename($name));
+ } elseif (false === strpos($name, '/')) {
+ // 自动加载公共模块下面的模型
+ if (!C('APP_USE_NAMESPACE')) {
+ import('Common/' . $layer . '/' . $class);
+ } else {
+ $class = '\\Common\\' . $layer . '\\' . $name . $layer;
+ }
+ $model = class_exists($class) ? new $class($name) : new Think\Model($name);
+ } else {
+ Think\Log::record('D方法实例化没找到模型类' . $class, Think\Log::NOTICE);
+ $model = new Think\Model(basename($name));
+ }
+ $_model[$name . $layer] = $model;
+ return $model;
+}
+
+/**
+ * 实例化一个没有模型文件的Model
+ * @param string $name Model名称 支持指定基础模型 例如 MongoModel:User
+ * @param string $tablePrefix 表前缀
+ * @param mixed $connection 数据库连接信息
+ * @return Think\Model
+ */
+function M($name = '', $tablePrefix = '', $connection = '')
+{
+ static $_model = array();
+ if (strpos($name, ':')) {
+ list($class, $name) = explode(':', $name);
+ } else {
+ $class = 'Think\\Model';
+ }
+ $guid = (is_array($connection) ? implode('', $connection) : $connection) . $tablePrefix . $name . '_' . $class;
+ if (!isset($_model[$guid])) {
+ $_model[$guid] = new $class($name, $tablePrefix, $connection);
+ }
+
+ return $_model[$guid];
+}
+
+/**
+ * 解析资源地址并导入类库文件
+ * 例如 module/controller addon://module/behavior
+ * @param string $name 资源地址 格式:[扩展://][模块/]资源名
+ * @param string $layer 分层名称
+ * @param integer $level 控制器层次
+ * @return string
+ */
+function parse_res_name($name, $layer, $level = 1)
+{
+ if (strpos($name, '://')) {
+ // 指定扩展资源
+ list($extend, $name) = explode('://', $name);
+ } else {
+ $extend = '';
+ }
+ if (strpos($name, '/') && substr_count($name, '/') >= $level) {
+ // 指定模块
+ list($module, $name) = explode('/', $name, 2);
+ } else {
+ $module = defined('MODULE_NAME') ? MODULE_NAME : '';
+ }
+ $array = explode('/', $name);
+ if (!C('APP_USE_NAMESPACE')) {
+ $class = parse_name($name, 1);
+ import($module . '/' . $layer . '/' . $class . $layer);
+ } else {
+ $class = $module . '\\' . $layer;
+ foreach ($array as $name) {
+ $class .= '\\' . parse_name($name, 1);
+ }
+ // 导入资源类库
+ if ($extend) {
+ // 扩展资源
+ $class = $extend . '\\' . $class;
+ }
+ }
+ return $class . $layer;
+}
+
+/**
+ * 用于实例化访问控制器
+ * @param string $name 控制器名
+ * @param string $path 控制器命名空间(路径)
+ * @return Think\Controller|false
+ */
+function controller($name, $path = '')
+{
+ $layer = C('DEFAULT_C_LAYER');
+ if (!C('APP_USE_NAMESPACE')) {
+ $class = parse_name($name, 1) . $layer;
+ import(MODULE_NAME . '/' . $layer . '/' . $class);
+ } else {
+ $class = ($path ? basename(ADDON_PATH) . '\\' . $path : MODULE_NAME) . '\\' . $layer;
+ $array = explode('/', $name);
+ foreach ($array as $name) {
+ $class .= '\\' . parse_name($name, 1);
+ }
+ $class .= $layer;
+ }
+ if (class_exists($class)) {
+ return new $class();
+ } else {
+ return false;
+ }
+}
+
+/**
+ * 实例化多层控制器 格式:[资源://][模块/]控制器
+ * @param string $name 资源地址
+ * @param string $layer 控制层名称
+ * @param integer $level 控制器层次
+ * @return Think\Controller|false
+ */
+function A($name, $layer = '', $level = 0)
+{
+ static $_action = array();
+ $layer = $layer ?: C('DEFAULT_C_LAYER');
+ $level = $level ?: (C('DEFAULT_C_LAYER') == $layer ? C('CONTROLLER_LEVEL') : 1);
+ if (isset($_action[$name . $layer])) {
+ return $_action[$name . $layer];
+ }
+
+ $class = parse_res_name($name, $layer, $level);
+ if (class_exists($class)) {
+ $action = new $class();
+ $_action[$name . $layer] = $action;
+ return $action;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * 远程调用控制器的操作方法 URL 参数格式 [资源://][模块/]控制器/操作
+ * @param string $url 调用地址
+ * @param string|array $vars 调用参数 支持字符串和数组
+ * @param string $layer 要调用的控制层名称
+ * @return mixed
+ */
+function R($url, $vars = array(), $layer = '')
+{
+ $info = pathinfo($url);
+ $action = $info['basename'];
+ $module = $info['dirname'];
+ $class = A($module, $layer);
+ if ($class) {
+ if (is_string($vars)) {
+ parse_str($vars, $vars);
+ }
+ return call_user_func_array(array(&$class, $action . C('ACTION_SUFFIX')), $vars);
+ } else {
+ return false;
+ }
+}
+
+/**
+ * 处理标签扩展
+ * @param string $tag 标签名称
+ * @param mixed $params 传入参数
+ * @return void
+ */
+function tag($tag, &$params = null)
+{
+ \Think\Hook::listen($tag, $params);
+}
+
+/**
+ * 执行某个行为
+ * @param string $name 行为名称
+ * @param string $tag 标签名称(行为类无需传入)
+ * @param Mixed $params 传入的参数
+ * @return void
+ */
+function B($name, $tag = '', &$params = null)
+{
+ if ('' == $tag) {
+ $name .= 'Behavior';
+ }
+ return \Think\Hook::exec($name, $tag, $params);
+}
+
+/**
+ * 去除代码中的空白和注释
+ * @param string $content 代码内容
+ * @return string
+ */
+function strip_whitespace($content)
+{
+ $stripStr = '';
+ //分析php源码
+ $tokens = token_get_all($content);
+ $last_space = false;
+ for ($i = 0, $j = count($tokens); $i < $j; $i++) {
+ if (is_string($tokens[$i])) {
+ $last_space = false;
+ $stripStr .= $tokens[$i];
+ } else {
+ switch ($tokens[$i][0]) {
+ //过滤各种PHP注释
+ case T_COMMENT:
+ case T_DOC_COMMENT:
+ break;
+ //过滤空格
+ case T_WHITESPACE:
+ if (!$last_space) {
+ $stripStr .= ' ';
+ $last_space = true;
+ }
+ break;
+ case T_START_HEREDOC:
+ $stripStr .= "<<' . $label . htmlspecialchars($output, ENT_QUOTES) . '';
+ } else {
+ $output = $label . print_r($var, true);
+ }
+ } else {
+ ob_start();
+ var_dump($var);
+ $output = ob_get_clean();
+ if (!extension_loaded('xdebug')) {
+ $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output);
+ $output = '' . $label . htmlspecialchars($output, ENT_QUOTES) . ' ';
+ }
+ }
+ if ($echo) {
+ echo ($output);
+ return null;
+ } else {
+ return $output;
+ }
+
+}
+
+/**
+ * 设置当前页面的布局
+ * @param string|false $layout 布局名称 为false的时候表示关闭布局
+ * @return void
+ */
+function layout($layout)
+{
+ if (false !== $layout) {
+ // 开启布局
+ C('LAYOUT_ON', true);
+ if (is_string($layout)) {
+ // 设置新的布局模板
+ C('LAYOUT_NAME', $layout);
+ }
+ } else {
+// 临时关闭布局
+ C('LAYOUT_ON', false);
+ }
+}
+
+/**
+ * URL组装 支持不同URL模式
+ * @param string $url URL表达式,格式:'[模块/控制器/操作#锚点@域名]?参数1=值1&参数2=值2...'
+ * @param string|array $vars 传入的参数,支持数组和字符串
+ * @param string|boolean $suffix 伪静态后缀,默认为true表示获取配置值
+ * @param boolean $domain 是否显示域名
+ * @return string
+ */
+function U($url = '', $vars = '', $suffix = true, $domain = false)
+{
+ // 解析URL
+ $info = parse_url($url);
+ $url = !empty($info['path']) ? $info['path'] : ACTION_NAME;
+ if (isset($info['fragment'])) {
+ // 解析锚点
+ $anchor = $info['fragment'];
+ if (false !== strpos($anchor, '?')) {
+ // 解析参数
+ list($anchor, $info['query']) = explode('?', $anchor, 2);
+ }
+ if (false !== strpos($anchor, '@')) {
+ // 解析域名
+ list($anchor, $host) = explode('@', $anchor, 2);
+ }
+ } elseif (false !== strpos($url, '@')) {
+ // 解析域名
+ list($url, $host) = explode('@', $info['path'], 2);
+ }
+ // 解析子域名
+ if (isset($host)) {
+ $domain = $host . (strpos($host, '.') ? '' : strstr($_SERVER['HTTP_HOST'], '.'));
+ } elseif (true === $domain) {
+ $domain = $_SERVER['HTTP_HOST'];
+ if (C('APP_SUB_DOMAIN_DEPLOY')) {
+ // 开启子域名部署 jry 598821125@qq.com 20160704
+ if (C('APP_DOMAIN_SUFFIX')) {
+ $domain_dot_count = substr_count(C('APP_DOMAIN_SUFFIX'), '.');
+ }
+ if ((1 + $domain_dot_count) === substr_count($_SERVER['HTTP_HOST'], '.')) {
+ $domain = 'localhost' == $domain ? 'localhost' : 'www.' . $_SERVER['HTTP_HOST'];
+ } else {
+ $domain = 'localhost' == $domain ? 'localhost' : 'www' . strstr($_SERVER['HTTP_HOST'], '.');
+ }
+ // '子域名'=>array('模块[/控制器]');
+ foreach (C('APP_SUB_DOMAIN_RULES') as $key => $rule) {
+ $rule = is_array($rule) ? $rule[0] : $rule;
+ if (false === strpos($key, '*') && 0 === strpos($url, $rule)) {
+ $domain = $key . strstr($domain, '.'); // 生成对应子域名
+ $url = substr_replace($url, '', 0, strlen($rule));
+ break;
+ }
+
+ // 子域名部署特殊转换 jry 598821125@qq.com 20160704
+ if (false === strpos($key, '*') && 0 === strpos($url, '/'.$rule)) {
+ $domain = $key . strstr($domain, '.'); // 生成对应子域名
+ $url = substr_replace($url, '', 0, strlen('/'.$rule));
+ break;
+ }
+ }
+ }
+ }
+
+ // 解析参数
+ if (is_string($vars)) {
+ // aaa=1&bbb=2 转换成数组
+ parse_str($vars, $vars);
+ } elseif (!is_array($vars)) {
+ $vars = array();
+ }
+ if (isset($info['query'])) {
+ // 解析地址里面参数 合并到vars
+ parse_str($info['query'], $params);
+ $vars = array_merge($params, $vars);
+ }
+
+ // URL组装
+ $depr = C('URL_PATHINFO_DEPR');
+ $urlCase = C('URL_CASE_INSENSITIVE');
+ if ($url) {
+ if (0 === strpos($url, '/')) {
+ // 定义路由
+ $route = true;
+ $url = substr($url, 1);
+ if ('/' != $depr) {
+ $url = str_replace('/', $depr, $url);
+ }
+ } else {
+ if ('/' != $depr) {
+ // 安全替换
+ $url = str_replace('/', $depr, $url);
+ }
+ // 解析模块、控制器和操作
+ $url = trim($url, $depr);
+ $path = explode($depr, $url);
+ $var = array();
+ $varModule = C('VAR_MODULE');
+ $varController = C('VAR_CONTROLLER');
+ $varAction = C('VAR_ACTION');
+ $var[$varAction] = !empty($path) ? array_pop($path) : ACTION_NAME;
+ $var[$varController] = !empty($path) ? array_pop($path) : CONTROLLER_NAME;
+ if ($maps = C('URL_ACTION_MAP')) {
+ if (isset($maps[strtolower($var[$varController])])) {
+ $maps = $maps[strtolower($var[$varController])];
+ if ($action = array_search(strtolower($var[$varAction]), $maps)) {
+ $var[$varAction] = $action;
+ }
+ }
+ }
+ if ($maps = C('URL_CONTROLLER_MAP')) {
+ if ($controller = array_search(strtolower($var[$varController]), $maps)) {
+ $var[$varController] = $controller;
+ }
+ }
+ if ($urlCase) {
+ $var[$varController] = parse_name($var[$varController]);
+ }
+ $module = '';
+
+ if (!empty($path)) {
+ $var[$varModule] = implode($depr, $path);
+ } else {
+ // 如果为插件,自动转换路径
+ if (CONTROLLER_PATH) {
+ $var[$varModule] = MODULE_NAME;
+ $varAddon = C('VAR_ADDON');
+ if (MODULE_NAME != C('DEFAULT_MODULE')) {
+ $var[$varController] = MODULE_NAME;
+ }
+
+ $vars = array_merge(array($varAddon => CONTROLLER_PATH), $vars);
+
+ } elseif (C('MULTI_MODULE')) {
+ if (MODULE_NAME != C('DEFAULT_MODULE') || !C('MODULE_ALLOW_LIST')) {
+ $var[$varModule] = MODULE_NAME;
+ }
+ }
+ }
+ if ($maps = C('URL_MODULE_MAP')) {
+ if ($_module = array_search(strtolower($var[$varModule]), $maps)) {
+ $var[$varModule] = $_module;
+ }
+ }
+ if (isset($var[$varModule])) {
+ $module = defined('BIND_MODULE') && BIND_MODULE == $var[$varModule] ? '' : $var[$varModule];
+ unset($var[$varModule]);
+ }
+
+ }
+ }
+
+ if (0 == C('URL_MODEL')) {
+ // 普通模式URL转换
+ $url = __APP__ . '?' . C('VAR_MODULE') . "={$module}&" . http_build_query(array_reverse($var));
+ if ($urlCase) {
+ $url = strtolower($url);
+ }
+ if (!empty($vars)) {
+ $vars = http_build_query($vars);
+ $url .= '&' . $vars;
+ }
+ } else {
+ // PATHINFO模式或者兼容URL模式
+ if (isset($route)) {
+ $url = __APP__ . '/' . rtrim($url, $depr);
+ } else {
+ $path = implode($depr, array_reverse($var));
+ if (C('URL_ROUTER_ON')) {
+ $url = Think\Route::reverse($path, $vars, $depr, $suffix);
+ if (!$url) {
+ $url = $path;
+ }
+ } else {
+ $url = $path;
+ }
+ $url = __APP__ . '/' . ($module ? $module . MODULE_PATHINFO_DEPR : '') . $url;
+ }
+ if ($urlCase) {
+ $url = strtolower($url);
+ }
+ if (!empty($vars)) {
+ // 添加参数
+ foreach ($vars as $var => $val) {
+ if ('' !== trim($val)) {
+ $url .= $depr . $var . $depr . urlencode($val);
+ }
+ }
+ }
+ if ($suffix) {
+ $suffix = true === $suffix ? C('URL_HTML_SUFFIX') : $suffix;
+ if ($pos = strpos($suffix, '|')) {
+ $suffix = substr($suffix, 0, $pos);
+ }
+ if ($suffix && '/' != substr($url, -1)) {
+ $url .= '.' . ltrim($suffix, '.');
+ }
+ }
+ }
+ if (!empty($anchor)) {
+ $url .= '#' . $anchor;
+ }
+ if ($domain) {
+ $url = (is_ssl() ? 'https://' : 'http://') . $domain . $url;
+ }
+ return $url;
+}
+
+/**
+ * 渲染输出Widget
+ * @param string $name Widget名称
+ * @param array $data 传入的参数
+ * @return void
+ */
+function W($name, $data = array())
+{
+ return R($name, $data, 'Widget');
+}
+
+/**
+ * 判断是否SSL协议
+ * @return boolean
+ */
+function is_ssl()
+{
+ if (isset($_SERVER['HTTPS']) && ('1' == $_SERVER['HTTPS'] || 'on' == strtolower($_SERVER['HTTPS']))) {
+ return true;
+ } elseif (isset($_SERVER['SERVER_PORT']) && ('443' == $_SERVER['SERVER_PORT'])) {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * URL重定向
+ * @param string $url 重定向的URL地址
+ * @param integer $time 重定向的等待时间(秒)
+ * @param string $msg 重定向前的提示信息
+ * @return void
+ */
+function redirect($url, $time = 0, $msg = '')
+{
+ //多行URL地址支持
+ $url = str_replace(array("\n", "\r"), '', $url);
+ if (empty($msg)) {
+ $msg = "系统将在{$time}秒之后自动跳转到{$url}!";
+ }
+
+ if (!headers_sent()) {
+ // redirect
+ if (0 === $time) {
+ header('Location: ' . $url);
+ } else {
+ header("refresh:{$time};url={$url}");
+ echo ($msg);
+ }
+ exit();
+ } else {
+ $str = " ";
+ if (0 != $time) {
+ $str .= $msg;
+ }
+
+ exit($str);
+ }
+}
+
+/**
+ * 缓存管理
+ * @param mixed $name 缓存名称,如果为数组表示进行缓存设置
+ * @param mixed $value 缓存值
+ * @param mixed $options 缓存参数
+ * @return mixed
+ */
+function S($name, $value = '', $options = null)
+{
+ static $cache = '';
+ if (is_array($options)) {
+ // 缓存操作的同时初始化
+ $type = isset($options['type']) ? $options['type'] : '';
+ $cache = Think\Cache::getInstance($type, $options);
+ } elseif (is_array($name)) {
+ // 缓存初始化
+ $type = isset($name['type']) ? $name['type'] : '';
+ $cache = Think\Cache::getInstance($type, $name);
+ return $cache;
+ } elseif (empty($cache)) {
+ // 自动初始化
+ $cache = Think\Cache::getInstance();
+ }
+ if ('' === $value) {
+ // 获取缓存
+ return $cache->get($name);
+ } elseif (is_null($value)) {
+ // 删除缓存
+ return $cache->rm($name);
+ } else {
+ // 缓存数据
+ if (is_array($options)) {
+ $expire = isset($options['expire']) ? $options['expire'] : null;
+ } else {
+ $expire = is_numeric($options) ? $options : null;
+ }
+ return $cache->set($name, $value, $expire);
+ }
+}
+
+/**
+ * 快速文件数据读取和保存 针对简单类型数据 字符串、数组
+ * @param string $name 缓存名称
+ * @param mixed $value 缓存值
+ * @param string $path 缓存路径
+ * @return mixed
+ */
+function F($name, $value = '', $path = DATA_PATH)
+{
+ static $_cache = array();
+ $filename = $path . $name . '.php';
+ if ('' !== $value) {
+ if (is_null($value)) {
+ // 删除缓存
+ if (false !== strpos($name, '*')) {
+ return false; // TODO
+ } else {
+ unset($_cache[$name]);
+ return Think\Storage::unlink($filename, 'F');
+ }
+ } else {
+ Think\Storage::put($filename, serialize($value), 'F');
+ // 缓存数据
+ $_cache[$name] = $value;
+ return null;
+ }
+ }
+ // 获取缓存数据
+ if (isset($_cache[$name])) {
+ return $_cache[$name];
+ }
+
+ if (Think\Storage::has($filename, 'F')) {
+ $value = unserialize(Think\Storage::read($filename, 'F'));
+ $_cache[$name] = $value;
+ } else {
+ $value = false;
+ }
+ return $value;
+}
+
+/**
+ * 根据PHP各种类型变量生成唯一标识号
+ * @param mixed $mix 变量
+ * @return string
+ */
+function to_guid_string($mix)
+{
+ if (is_object($mix)) {
+ return spl_object_hash($mix);
+ } elseif (is_resource($mix)) {
+ $mix = get_resource_type($mix) . strval($mix);
+ } else {
+ $mix = serialize($mix);
+ }
+ return md5($mix);
+}
+
+/**
+ * XML编码
+ * @param mixed $data 数据
+ * @param string $root 根节点名
+ * @param string $item 数字索引的子节点名
+ * @param string $attr 根节点属性
+ * @param string $id 数字索引子节点key转换的属性名
+ * @param string $encoding 数据编码
+ * @return string
+ */
+function xml_encode($data, $root = 'think', $item = 'item', $attr = '', $id = 'id', $encoding = 'utf-8')
+{
+ if (is_array($attr)) {
+ $_attr = array();
+ foreach ($attr as $key => $value) {
+ $_attr[] = "{$key}=\"{$value}\"";
+ }
+ $attr = implode(' ', $_attr);
+ }
+ $attr = trim($attr);
+ $attr = empty($attr) ? '' : " {$attr}";
+ $xml = "";
+ $xml .= "<{$root}{$attr}>";
+ $xml .= data_to_xml($data, $item, $id);
+ $xml .= "{$root}>";
+ return $xml;
+}
+
+/**
+ * 数据XML编码
+ * @param mixed $data 数据
+ * @param string $item 数字索引时的节点名称
+ * @param string $id 数字索引key转换为的属性名
+ * @return string
+ */
+function data_to_xml($data, $item = 'item', $id = 'id')
+{
+ $xml = $attr = '';
+ foreach ($data as $key => $val) {
+ if (is_numeric($key)) {
+ $id && $attr = " {$id}=\"{$key}\"";
+ $key = $item;
+ }
+ $xml .= "<{$key}{$attr}>";
+ $xml .= (is_array($val) || is_object($val)) ? data_to_xml($val, $item, $id) : $val;
+ $xml .= "{$key}>";
+ }
+ return $xml;
+}
+
+/**
+ * session管理函数
+ * @param string|array $name session名称 如果为数组则表示进行session设置
+ * @param mixed $value session值
+ * @return mixed
+ */
+function session($name = '', $value = '')
+{
+ $prefix = C('SESSION_PREFIX');
+ if (is_array($name)) {
+ // session初始化 在session_start 之前调用
+ if (isset($name['prefix'])) {
+ C('SESSION_PREFIX', $name['prefix']);
+ }
+
+ if (C('VAR_SESSION_ID') && isset($_REQUEST[C('VAR_SESSION_ID')])) {
+ session_id($_REQUEST[C('VAR_SESSION_ID')]);
+ } elseif (isset($name['id'])) {
+ session_id($name['id']);
+ }
+ if ('common' == APP_MODE) {
+ // 其它模式可能不支持
+ ini_set('session.auto_start', 0);
+ }
+ if (isset($name['name'])) {
+ session_name($name['name']);
+ }
+
+ if (isset($name['path'])) {
+ session_save_path($name['path']);
+ }
+
+ if (isset($name['domain'])) {
+ ini_set('session.cookie_domain', $name['domain']);
+ }
+
+ if (isset($name['expire'])) {
+ ini_set('session.gc_maxlifetime', $name['expire']);
+ ini_set('session.cookie_lifetime', $name['expire']);
+ }
+ if (isset($name['use_trans_sid'])) {
+ ini_set('session.use_trans_sid', $name['use_trans_sid'] ? 1 : 0);
+ }
+
+ if (isset($name['use_cookies'])) {
+ ini_set('session.use_cookies', $name['use_cookies'] ? 1 : 0);
+ }
+
+ if (isset($name['cache_limiter'])) {
+ session_cache_limiter($name['cache_limiter']);
+ }
+
+ if (isset($name['cache_expire'])) {
+ session_cache_expire($name['cache_expire']);
+ }
+
+ if (isset($name['type'])) {
+ C('SESSION_TYPE', $name['type']);
+ }
+
+ if (C('SESSION_TYPE')) {
+ // 读取session驱动
+ $type = C('SESSION_TYPE');
+ $class = strpos($type, '\\') ? $type : 'Think\\Session\\Driver\\' . ucwords(strtolower($type));
+ $hander = new $class();
+ session_set_save_handler(
+ array(&$hander, "open"),
+ array(&$hander, "close"),
+ array(&$hander, "read"),
+ array(&$hander, "write"),
+ array(&$hander, "destroy"),
+ array(&$hander, "gc"));
+ }
+ // 启动session
+ if (C('SESSION_AUTO_START')) {
+ session_start();
+ }
+
+ } elseif ('' === $value) {
+ if ('' === $name) {
+ // 获取全部的session
+ return $prefix ? $_SESSION[$prefix] : $_SESSION;
+ } elseif (0 === strpos($name, '[')) {
+ // session 操作
+ if ('[pause]' == $name) {
+ // 暂停session
+ session_write_close();
+ } elseif ('[start]' == $name) {
+ // 启动session
+ session_start();
+ } elseif ('[destroy]' == $name) {
+ // 销毁session
+ $_SESSION = array();
+ session_unset();
+ session_destroy();
+ } elseif ('[regenerate]' == $name) {
+ // 重新生成id
+ session_regenerate_id();
+ }
+ } elseif (0 === strpos($name, '?')) {
+ // 检查session
+ $name = substr($name, 1);
+ if (strpos($name, '.')) {
+ // 支持数组
+ list($name1, $name2) = explode('.', $name);
+ return $prefix ? isset($_SESSION[$prefix][$name1][$name2]) : isset($_SESSION[$name1][$name2]);
+ } else {
+ return $prefix ? isset($_SESSION[$prefix][$name]) : isset($_SESSION[$name]);
+ }
+ } elseif (is_null($name)) {
+ // 清空session
+ if ($prefix) {
+ unset($_SESSION[$prefix]);
+ } else {
+ $_SESSION = array();
+ }
+ } elseif ($prefix) {
+ // 获取session
+ if (strpos($name, '.')) {
+ list($name1, $name2) = explode('.', $name);
+ return isset($_SESSION[$prefix][$name1][$name2]) ? $_SESSION[$prefix][$name1][$name2] : null;
+ } else {
+ return isset($_SESSION[$prefix][$name]) ? $_SESSION[$prefix][$name] : null;
+ }
+ } else {
+ if (strpos($name, '.')) {
+ list($name1, $name2) = explode('.', $name);
+ return isset($_SESSION[$name1][$name2]) ? $_SESSION[$name1][$name2] : null;
+ } else {
+ return isset($_SESSION[$name]) ? $_SESSION[$name] : null;
+ }
+ }
+ } elseif (is_null($value)) {
+ // 删除session
+ if (strpos($name, '.')) {
+ list($name1, $name2) = explode('.', $name);
+ if ($prefix) {
+ unset($_SESSION[$prefix][$name1][$name2]);
+ } else {
+ unset($_SESSION[$name1][$name2]);
+ }
+ } else {
+ if ($prefix) {
+ unset($_SESSION[$prefix][$name]);
+ } else {
+ unset($_SESSION[$name]);
+ }
+ }
+ } else {
+ // 设置session
+ if (strpos($name, '.')) {
+ list($name1, $name2) = explode('.', $name);
+ if ($prefix) {
+ $_SESSION[$prefix][$name1][$name2] = $value;
+ } else {
+ $_SESSION[$name1][$name2] = $value;
+ }
+ } else {
+ if ($prefix) {
+ $_SESSION[$prefix][$name] = $value;
+ } else {
+ $_SESSION[$name] = $value;
+ }
+ }
+ }
+ return null;
+}
+
+/**
+ * Cookie 设置、获取、删除
+ * @param string $name cookie名称
+ * @param mixed $value cookie值
+ * @param mixed $option cookie参数
+ * @return mixed
+ */
+function cookie($name = '', $value = '', $option = null)
+{
+ // 默认设置
+ $config = array(
+ 'prefix' => C('COOKIE_PREFIX'), // cookie 名称前缀
+ 'expire' => C('COOKIE_EXPIRE'), // cookie 保存时间
+ 'path' => C('COOKIE_PATH'), // cookie 保存路径
+ 'domain' => C('COOKIE_DOMAIN'), // cookie 有效域名
+ 'secure' => C('COOKIE_SECURE'), // cookie 启用安全传输
+ 'httponly' => C('COOKIE_HTTPONLY'), // httponly设置
+ );
+ // 参数设置(会覆盖黙认设置)
+ if (!is_null($option)) {
+ if (is_numeric($option)) {
+ $option = array('expire' => $option);
+ } elseif (is_string($option)) {
+ parse_str($option, $option);
+ }
+
+ $config = array_merge($config, array_change_key_case($option));
+ }
+ if (!empty($config['httponly'])) {
+ ini_set("session.cookie_httponly", 1);
+ }
+ // 清除指定前缀的所有cookie
+ if (is_null($name)) {
+ if (empty($_COOKIE)) {
+ return null;
+ }
+
+ // 要删除的cookie前缀,不指定则删除config设置的指定前缀
+ $prefix = empty($value) ? $config['prefix'] : $value;
+ if (!empty($prefix)) {
+ // 如果前缀为空字符串将不作处理直接返回
+ foreach ($_COOKIE as $key => $val) {
+ if (0 === stripos($key, $prefix)) {
+ setcookie($key, '', time() - 3600, $config['path'], $config['domain'], $config['secure'], $config['httponly']);
+ unset($_COOKIE[$key]);
+ }
+ }
+ }
+ return null;
+ } elseif ('' === $name) {
+ // 获取全部的cookie
+ return $_COOKIE;
+ }
+ $name = $config['prefix'] . str_replace('.', '_', $name);
+ if ('' === $value) {
+ if (isset($_COOKIE[$name])) {
+ $value = $_COOKIE[$name];
+ if (0 === strpos($value, 'think:')) {
+ $value = substr($value, 6);
+ return array_map('urldecode', json_decode(MAGIC_QUOTES_GPC ? stripslashes($value) : $value, true));
+ } else {
+ return $value;
+ }
+ } else {
+ return null;
+ }
+ } else {
+ if (is_null($value)) {
+ setcookie($name, '', time() - 3600, $config['path'], $config['domain'], $config['secure'], $config['httponly']);
+ unset($_COOKIE[$name]); // 删除指定cookie
+ } else {
+ // 设置cookie
+ if (is_array($value)) {
+ $value = 'think:' . json_encode(array_map('urlencode', $value));
+ }
+ $expire = !empty($config['expire']) ? time() + intval($config['expire']) : 0;
+ setcookie($name, $value, $expire, $config['path'], $config['domain'], $config['secure'], $config['httponly']);
+ $_COOKIE[$name] = $value;
+ }
+ }
+ return null;
+}
+
+/**
+ * 加载动态扩展文件
+ * @var string $path 文件路径
+ * @return void
+ */
+function load_ext_file($path)
+{
+ // 加载自定义外部文件
+ if ($files = C('LOAD_EXT_FILE')) {
+ $files = explode(',', $files);
+ foreach ($files as $file) {
+ $file = $path . 'Common/' . $file . '.php';
+ if (is_file($file)) {
+ include $file;
+ }
+
+ }
+ }
+ // 加载自定义的动态配置文件
+ if ($configs = C('LOAD_EXT_CONFIG')) {
+ if (is_string($configs)) {
+ $configs = explode(',', $configs);
+ }
+
+ foreach ($configs as $key => $config) {
+ $file = is_file($config) ? $config : $path . 'Conf/' . $config . CONF_EXT;
+ if (is_file($file)) {
+ is_numeric($key) ? C(load_Config($file)) : C($key, load_Config($file));
+ }
+ }
+ }
+}
+
+/**
+ * 获取客户端IP地址
+ * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字
+ * @param boolean $adv 是否进行高级模式获取(有可能被伪装)
+ * @return mixed
+ */
+function get_client_ip($type = 0, $adv = false)
+{
+ $type = $type ? 1 : 0;
+ static $ip = null;
+ if (null !== $ip) {
+ return $ip[$type];
+ }
+
+ if ($adv) {
+ if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+ $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
+ $pos = array_search('unknown', $arr);
+ if (false !== $pos) {
+ unset($arr[$pos]);
+ }
+
+ $ip = trim($arr[0]);
+ } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
+ $ip = $_SERVER['HTTP_CLIENT_IP'];
+ } elseif (isset($_SERVER['REMOTE_ADDR'])) {
+ $ip = $_SERVER['REMOTE_ADDR'];
+ }
+ } elseif (isset($_SERVER['REMOTE_ADDR'])) {
+ $ip = $_SERVER['REMOTE_ADDR'];
+ }
+ // IP地址合法验证
+ $long = sprintf("%u", ip2long($ip));
+ $ip = $long ? array($ip, $long) : array('0.0.0.0', 0);
+ return $ip[$type];
+}
+
+/**
+ * 发送HTTP状态
+ * @param integer $code 状态码
+ * @return void
+ */
+function send_http_status($code)
+{
+ static $_status = array(
+ // Informational 1xx
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ // Success 2xx
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ // Redirection 3xx
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Moved Temporarily ', // 1.1
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ // 306 is deprecated but reserved
+ 307 => 'Temporary Redirect',
+ // Client Error 4xx
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ // Server Error 5xx
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ 509 => 'Bandwidth Limit Exceeded',
+ );
+ if (isset($_status[$code])) {
+ header('HTTP/1.1 ' . $code . ' ' . $_status[$code]);
+ // 确保FastCGI模式下正常
+ header('Status:' . $code . ' ' . $_status[$code]);
+ }
+}
+
+function think_filter(&$value)
+{
+ // TODO 其他安全过滤
+
+ // 过滤查询特殊字符
+ if (preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i', $value)) {
+ $value .= ' ';
+ }
+}
+
+// 不区分大小写的in_array实现
+function in_array_case($value, $array)
+{
+ return in_array(strtolower($value), array_map('strtolower', $array));
+}
diff --git a/Framework/Conf/convention.php b/Framework/Conf/convention.php
new file mode 100644
index 00000000..8da40fcc
--- /dev/null
+++ b/Framework/Conf/convention.php
@@ -0,0 +1,167 @@
+
+// +----------------------------------------------------------------------
+
+/**
+ * ThinkPHP惯例配置文件
+ * 该文件请不要修改,如果要覆盖惯例配置的值,可在应用配置文件中设定和惯例不符的配置项
+ * 配置名称大小写任意,系统会统一转换成小写
+ * 所有配置参数都可以在生效前动态改变
+ */
+
+return array(
+ /* 应用设定 */
+ 'APP_USE_NAMESPACE' => true, // 应用类库是否使用命名空间
+ 'APP_SUB_DOMAIN_DEPLOY' => false, // 是否开启子域名部署
+ 'APP_SUB_DOMAIN_RULES' => array(), // 子域名部署规则
+ 'APP_DOMAIN_SUFFIX' => '', // 域名后缀 如果是com.cn net.cn 之类的后缀必须设置
+ 'ACTION_SUFFIX' => '', // 操作方法后缀
+ 'MULTI_MODULE' => true, // 是否允许多模块 如果为false 则必须设置 DEFAULT_MODULE
+ 'MODULE_DENY_LIST' => array('Common', 'Runtime'),
+ 'CONTROLLER_LEVEL' => 1,
+ 'APP_AUTOLOAD_LAYER' => 'Controller,Model', // 自动加载的应用类库层 关闭APP_USE_NAMESPACE后有效
+ 'APP_AUTOLOAD_PATH' => '', // 自动加载的路径 关闭APP_USE_NAMESPACE后有效
+
+ /* Cookie设置 */
+ 'COOKIE_EXPIRE' => 0, // Cookie有效期
+ 'COOKIE_DOMAIN' => '', // Cookie有效域名
+ 'COOKIE_PATH' => '/', // Cookie路径
+ 'COOKIE_PREFIX' => '', // Cookie前缀 避免冲突
+ 'COOKIE_SECURE' => false, // Cookie安全传输
+ 'COOKIE_HTTPONLY' => '', // Cookie httponly设置
+
+ /* 默认设定 */
+ 'DEFAULT_M_LAYER' => 'Model', // 默认的模型层名称
+ 'DEFAULT_C_LAYER' => 'Controller', // 默认的控制器层名称
+ 'DEFAULT_V_LAYER' => 'View', // 默认的视图层名称
+ 'DEFAULT_LANG' => 'zh-cn', // 默认语言
+ 'DEFAULT_THEME' => '', // 默认模板主题名称
+ 'DEFAULT_MODULE' => 'Home', // 默认模块
+ 'DEFAULT_CONTROLLER' => 'Index', // 默认控制器名称
+ 'DEFAULT_ACTION' => 'index', // 默认操作名称
+ 'DEFAULT_CHARSET' => 'utf-8', // 默认输出编码
+ 'DEFAULT_TIMEZONE' => 'PRC', // 默认时区
+ 'DEFAULT_AJAX_RETURN' => 'JSON', // 默认AJAX 数据返回格式,可选JSON XML ...
+ 'DEFAULT_JSONP_HANDLER' => 'jsonpReturn', // 默认JSONP格式返回的处理方法
+ 'DEFAULT_FILTER' => 'htmlspecialchars', // 默认参数过滤方法 用于I函数...
+
+ /* 数据库设置 */
+ 'DB_TYPE' => '', // 数据库类型
+ 'DB_HOST' => '', // 服务器地址
+ 'DB_NAME' => '', // 数据库名
+ 'DB_USER' => '', // 用户名
+ 'DB_PWD' => '', // 密码
+ 'DB_PORT' => '', // 端口
+ 'DB_PREFIX' => '', // 数据库表前缀
+ 'DB_PARAMS' => array(), // 数据库连接参数
+ 'DB_DEBUG' => true, // 数据库调试模式 开启后可以记录SQL日志
+ 'DB_FIELDS_CACHE' => true, // 启用字段缓存
+ 'DB_CHARSET' => 'utf8', // 数据库编码默认采用utf8
+ 'DB_DEPLOY_TYPE' => 0, // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
+ 'DB_RW_SEPARATE' => false, // 数据库读写是否分离 主从式有效
+ 'DB_MASTER_NUM' => 1, // 读写分离后 主服务器数量
+ 'DB_SLAVE_NO' => '', // 指定从服务器序号
+
+ /* 数据缓存设置 */
+ 'DATA_CACHE_TIME' => 0, // 数据缓存有效期 0表示永久缓存
+ 'DATA_CACHE_COMPRESS' => false, // 数据缓存是否压缩缓存
+ 'DATA_CACHE_CHECK' => false, // 数据缓存是否校验缓存
+ 'DATA_CACHE_PREFIX' => '', // 缓存前缀
+ 'DATA_CACHE_TYPE' => 'File', // 数据缓存类型,支持:File|Db|Apc|Memcache|Shmop|Sqlite|Xcache|Apachenote|Eaccelerator
+ 'DATA_CACHE_PATH' => TEMP_PATH, // 缓存路径设置 (仅对File方式缓存有效)
+ 'DATA_CACHE_KEY' => '', // 缓存文件KEY (仅对File方式缓存有效)
+ 'DATA_CACHE_SUBDIR' => false, // 使用子目录缓存 (自动根据缓存标识的哈希创建子目录)
+ 'DATA_PATH_LEVEL' => 1, // 子目录缓存级别
+
+ /* 错误设置 */
+ 'ERROR_MESSAGE' => '页面错误!请稍后再试~', //错误显示信息,非调试模式有效
+ 'ERROR_PAGE' => '', // 错误定向页面
+ 'SHOW_ERROR_MSG' => false, // 显示错误信息
+ 'TRACE_MAX_RECORD' => 100, // 每个级别的错误信息 最大记录数
+
+ /* 日志设置 */
+ 'LOG_RECORD' => false, // 默认不记录日志
+ 'LOG_TYPE' => 'File', // 日志记录类型 默认为文件方式
+ 'LOG_LEVEL' => 'EMERG,ALERT,CRIT,ERR', // 允许记录的日志级别
+ 'LOG_FILE_SIZE' => 2097152, // 日志文件大小限制
+ 'LOG_EXCEPTION_RECORD' => false, // 是否记录异常信息日志
+
+ /* SESSION设置 */
+ 'SESSION_AUTO_START' => true, // 是否自动开启Session
+ 'SESSION_OPTIONS' => array(), // session 配置数组 支持type name id path expire domain 等参数
+ 'SESSION_TYPE' => '', // session handler类型 默认无需设置 除非扩展了session handler驱动
+ 'SESSION_PREFIX' => '', // session 前缀
+ //'VAR_SESSION_ID' => 'session_id', //sessionID的提交变量
+
+ /* 模板引擎设置 */
+ 'TMPL_CONTENT_TYPE' => 'text/html', // 默认模板输出类型
+ 'TMPL_ACTION_ERROR' => THINK_PATH . 'Tpl/dispatch_jump.tpl', // 默认错误跳转对应的模板文件
+ 'TMPL_ACTION_SUCCESS' => THINK_PATH . 'Tpl/dispatch_jump.tpl', // 默认成功跳转对应的模板文件
+ 'TMPL_EXCEPTION_FILE' => THINK_PATH . 'Tpl/think_exception.tpl', // 异常页面的模板文件
+ 'TMPL_DETECT_THEME' => false, // 自动侦测模板主题
+ 'TMPL_TEMPLATE_SUFFIX' => '.html', // 默认模板文件后缀
+ 'TMPL_FILE_DEPR' => '/', //模板文件CONTROLLER_NAME与ACTION_NAME之间的分割符
+ // 布局设置
+ 'TMPL_ENGINE_TYPE' => 'Think', // 默认模板引擎 以下设置仅对使用Think模板引擎有效
+ 'TMPL_CACHFILE_SUFFIX' => '.php', // 默认模板缓存后缀
+ 'TMPL_DENY_FUNC_LIST' => 'echo,exit', // 模板引擎禁用函数
+ 'TMPL_DENY_PHP' => false, // 默认模板引擎是否禁用PHP原生代码
+ 'TMPL_L_DELIM' => '{', // 模板引擎普通标签开始标记
+ 'TMPL_R_DELIM' => '}', // 模板引擎普通标签结束标记
+ 'TMPL_VAR_IDENTIFY' => 'array', // 模板变量识别。留空自动判断,参数为'obj'则表示对象
+ 'TMPL_STRIP_SPACE' => true, // 是否去除模板文件里面的html空格与换行
+ 'TMPL_CACHE_ON' => true, // 是否开启模板编译缓存,设为false则每次都会重新编译
+ 'TMPL_CACHE_PREFIX' => '', // 模板缓存前缀标识,可以动态改变
+ 'TMPL_CACHE_TIME' => 0, // 模板缓存有效期 0 为永久,(以数字为值,单位:秒)
+ 'TMPL_LAYOUT_ITEM' => '{__CONTENT__}', // 布局模板的内容替换标识
+ 'LAYOUT_ON' => false, // 是否启用布局
+ 'LAYOUT_NAME' => 'layout', // 当前布局名称 默认为layout
+
+ // Think模板引擎标签库相关设定
+ 'TAGLIB_BEGIN' => '<', // 标签库标签开始标记
+ 'TAGLIB_END' => '>', // 标签库标签结束标记
+ 'TAGLIB_LOAD' => true, // 是否使用内置标签库之外的其它标签库,默认自动检测
+ 'TAGLIB_BUILD_IN' => 'cx', // 内置标签库名称(标签使用不必指定标签库名称),以逗号分隔 注意解析顺序
+ 'TAGLIB_PRE_LOAD' => '', // 需要额外加载的标签库(须指定标签库名称),多个以逗号分隔
+
+ /* URL设置 */
+ 'URL_CASE_INSENSITIVE' => true, // 默true 表示URL不区分大小写 false则表示区分大小写
+ 'URL_MODEL' => 1, // URL访问模式,可选参数0、1、2、3,代表以下四种模式:
+ // 0 (普通模式); 1 (PATHINFO 模式); 2 (REWRITE 模式); 3 (兼容模式) 默认为PATHINFO 模式
+ 'URL_PATHINFO_DEPR' => '/', // PATHINFO模式下,各参数之间的分割符号
+ 'URL_PATHINFO_FETCH' => 'ORIG_PATH_INFO,REDIRECT_PATH_INFO,REDIRECT_URL', // 用于兼容判断PATH_INFO 参数的SERVER替代变量列表
+ 'URL_REQUEST_URI' => 'REQUEST_URI', // 获取当前页面地址的系统变量 默认为REQUEST_URI
+ 'URL_HTML_SUFFIX' => 'html', // URL伪静态后缀设置
+ 'URL_DENY_SUFFIX' => 'ico|png|gif|jpg', // URL禁止访问的后缀设置
+ 'URL_PARAMS_BIND' => true, // URL变量绑定到Action方法参数
+ 'URL_PARAMS_BIND_TYPE' => 0, // URL变量绑定的类型 0 按变量名绑定 1 按变量顺序绑定
+ 'URL_PARAMS_FILTER' => false, // URL变量绑定过滤
+ 'URL_PARAMS_FILTER_TYPE' => '', // URL变量绑定过滤方法 如果为空 调用DEFAULT_FILTER
+ 'URL_ROUTER_ON' => false, // 是否开启URL路由
+ 'URL_ROUTE_RULES' => array(), // 默认路由规则 针对模块
+ 'URL_MAP_RULES' => array(), // URL映射定义规则
+
+ /* 系统变量名称设置 */
+ 'VAR_MODULE' => 'm', // 默认模块获取变量
+ 'VAR_ADDON' => 'addon', // 默认的插件控制器命名空间变量
+ 'VAR_CONTROLLER' => 'c', // 默认控制器获取变量
+ 'VAR_ACTION' => 'a', // 默认操作获取变量
+ 'VAR_AJAX_SUBMIT' => 'ajax', // 默认的AJAX提交变量
+ 'VAR_JSONP_HANDLER' => 'callback',
+ 'VAR_PATHINFO' => 's', // 兼容模式PATHINFO获取变量例如 ?s=/module/action/id/1 后面的参数取决于URL_PATHINFO_DEPR
+ 'VAR_TEMPLATE' => 't', // 默认模板切换变量
+ 'VAR_AUTO_STRING' => false, // 输入变量是否自动强制转换为字符串 如果开启则数组变量需要手动传入变量修饰符获取变量
+
+ 'HTTP_CACHE_CONTROL' => 'private', // 网页缓存控制
+ 'CHECK_APP_DIR' => true, // 是否检查应用目录是否创建
+ 'FILE_UPLOAD_TYPE' => 'Local', // 文件上传方式
+ 'DATA_CRYPT_TYPE' => 'Think', // 数据加密方式
+
+);
diff --git a/Framework/Conf/debug.php b/Framework/Conf/debug.php
new file mode 100644
index 00000000..39957ced
--- /dev/null
+++ b/Framework/Conf/debug.php
@@ -0,0 +1,27 @@
+
+// +----------------------------------------------------------------------
+
+/**
+ * ThinkPHP 默认的调试模式配置文件
+ */
+
+// 调试模式下面默认设置 可以在应用配置目录下重新定义 debug.php 覆盖
+return array(
+ 'LOG_RECORD' => true, // 进行日志记录
+ 'LOG_EXCEPTION_RECORD' => true, // 是否记录异常信息日志
+ 'LOG_LEVEL' => 'EMERG,ALERT,CRIT,ERR,WARN,NOTIC,INFO,DEBUG,SQL', // 允许记录的日志级别
+ 'DB_FIELDS_CACHE' => false, // 字段缓存信息
+ 'DB_DEBUG' => true, // 开启调试模式 记录SQL日志
+ 'TMPL_CACHE_ON' => false, // 是否开启模板编译缓存,设为false则每次都会重新编译
+ 'TMPL_STRIP_SPACE' => false, // 是否去除模板文件里面的html空格与换行
+ 'SHOW_ERROR_MSG' => true, // 显示错误信息
+ 'URL_CASE_INSENSITIVE' => false, // URL区分大小写
+);
diff --git a/Framework/LICENSE.txt b/Framework/LICENSE.txt
new file mode 100644
index 00000000..581f906a
--- /dev/null
+++ b/Framework/LICENSE.txt
@@ -0,0 +1,32 @@
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+版权所有Copyright © 2006-2014 by ThinkPHP (http://thinkphp.cn)
+All rights reserved。
+ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
+
+Apache Licence是著名的非盈利开源组织Apache采用的协议。
+该协议和BSD类似,鼓励代码共享和尊重原作者的著作权,
+允许代码修改,再作为开源或商业软件发布。需要满足
+的条件:
+1. 需要给代码的用户一份Apache Licence ;
+2. 如果你修改了代码,需要在被修改的文件中说明;
+3. 在延伸的代码中(修改和有源代码衍生的代码中)需要
+带有原来代码中的协议,商标,专利声明和其他原来作者规
+定需要包含的说明;
+4. 如果再发布的产品中包含一个Notice文件,则在Notice文
+件中需要带有本协议内容。你可以在Notice中增加自己的
+许可,但不可以表现为对Apache Licence构成更改。
+具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/Framework/Lang/en-us.php b/Framework/Lang/en-us.php
new file mode 100644
index 00000000..bc29609b
--- /dev/null
+++ b/Framework/Lang/en-us.php
@@ -0,0 +1,51 @@
+
+// +----------------------------------------------------------------------
+
+/**
+ * ThinkPHP English language package
+ */
+return array(
+ /* core language package */
+ '_MODULE_NOT_EXIST_' => "Module can't be loaded",
+ '_CONTROLLER_NOT_EXIST_' => "Controller can't be loaded",
+ '_ERROR_ACTION_' => 'Illegal Action',
+ '_LANGUAGE_NOT_LOAD_' => "Can't load language package",
+ '_TEMPLATE_NOT_EXIST_' => "Template doesn't exist",
+ '_MODULE_' => 'Module',
+ '_ACTION_' => 'Action',
+ '_MODEL_NOT_EXIST_' => "Model can't be loaded",
+ '_VALID_ACCESS_' => 'No access',
+ '_XML_TAG_ERROR_' => 'XML tag syntax errors',
+ '_DATA_TYPE_INVALID_' => 'Illegal data objects!',
+ '_OPERATION_WRONG_' => 'Operation error occurs',
+ '_NOT_LOAD_DB_' => 'Unable to load the database',
+ '_NO_DB_DRIVER_' => 'Unable to load database driver',
+ '_NOT_SUPPORT_DB_' => 'The system is temporarily not support database',
+ '_NO_DB_CONFIG_' => 'Not define the database configuration',
+ '_NOT_SUPPORT_' => 'The system does not support',
+ '_CACHE_TYPE_INVALID_' => 'Unable to load the cache type',
+ '_FILE_NOT_WRITABLE_' => 'Directory (file) is not writable',
+ '_METHOD_NOT_EXIST_' => 'The method you requested does not exist!',
+ '_CLASS_NOT_EXIST_' => 'Instantiating a class does not exist!',
+ '_CLASS_CONFLICT_' => 'Class name conflicts',
+ '_TEMPLATE_ERROR_' => 'Template Engine errors',
+ '_CACHE_WRITE_ERROR_' => 'Cache file write failed!',
+ '_TAGLIB_NOT_EXIST_' => 'Tag library is not defined',
+ '_OPERATION_FAIL_' => 'Operation failed!',
+ '_OPERATION_SUCCESS_' => 'Operation succeed!',
+ '_SELECT_NOT_EXIST_' => 'Record does not exist!',
+ '_EXPRESS_ERROR_' => 'Expression errors',
+ '_TOKEN_ERROR_' => "Form's token errors",
+ '_RECORD_HAS_UPDATE_' => 'Record has been updated',
+ '_NOT_ALLOW_PHP_' => 'PHP codes are not allowed in the template',
+ '_PARAM_ERROR_' => 'Parameter error or undefined',
+ '_ERROR_QUERY_EXPRESS_' => 'Query express error',
+);
diff --git a/Framework/Lang/pt-br.php b/Framework/Lang/pt-br.php
new file mode 100644
index 00000000..0569a037
--- /dev/null
+++ b/Framework/Lang/pt-br.php
@@ -0,0 +1,51 @@
+
+// +----------------------------------------------------------------------
+
+/**
+ * ThinkPHP Portuguese language package
+ */
+return array(
+ /* core language package */
+ '_MODULE_NOT_EXIST_' => "Módulo não pode ser carregado",
+ '_CONTROLLER_NOT_EXIST_' => "Controller não pode ser carregado",
+ '_ERROR_ACTION_' => 'Ação ilegal',
+ '_LANGUAGE_NOT_LOAD_' => "Não é possível carregar pacote da linguagem",
+ '_TEMPLATE_NOT_EXIST_' => "Template não existe",
+ '_MODULE_' => 'Módulo',
+ '_ACTION_' => 'Ação',
+ '_MODEL_NOT_EXIST_' => "Modelo não pode ser carregado",
+ '_VALID_ACCESS_' => 'Sem acesso',
+ '_XML_TAG_ERROR_' => 'Erro de sintaxe - XML tag',
+ '_DATA_TYPE_INVALID_' => 'Tipos de dados ilegais!',
+ '_OPERATION_WRONG_' => 'Erro na operação',
+ '_NOT_LOAD_DB_' => 'Impossível carregar banco de dados',
+ '_NO_DB_DRIVER_' => 'Impossível carregar driver do bando de dados',
+ '_NOT_SUPPORT_DB_' => 'Temporariamente sem suporte ao banco',
+ '_NO_DB_CONFIG_' => 'Não define a configuração do banco',
+ '_NOT_SUPPORT_' => 'O sistema não suporta',
+ '_CACHE_TYPE_INVALID_' => 'Impossível carregar o tipo de cache',
+ '_FILE_NOT_WRITABLE_' => 'Diretório (arquivo) não pode ser escrito',
+ '_METHOD_NOT_EXIST_' => 'O método solicitado não existe!',
+ '_CLASS_NOT_EXIST_' => 'Não existe instância da classe',
+ '_CLASS_CONFLICT_' => 'Conflitos com nome da classe',
+ '_TEMPLATE_ERROR_' => 'Erros na contrução do template',
+ '_CACHE_WRITE_ERROR_' => 'Escrita do arquivo de cache falhou!',
+ '_TAGLIB_NOT_EXIST_' => 'Biblioteca da tag não foi definida',
+ '_OPERATION_FAIL_' => 'Operação falhou!',
+ '_OPERATION_SUCCESS_' => 'Operação bem sucessida!',
+ '_SELECT_NOT_EXIST_' => 'Gravação não existe!',
+ '_EXPRESS_ERROR_' => 'Erros de expressão',
+ '_TOKEN_ERROR_' => 'Erro no token do formulário',
+ '_RECORD_HAS_UPDATE_' => 'Gravação não foi atualizada',
+ '_NOT_ALLOW_PHP_' => 'Código PHP não é permitido no template',
+ '_PARAM_ERROR_' => 'Parâmetro errado ou indefinido',
+ '_ERROR_QUERY_EXPRESS_' => 'Erros na expressão da query',
+);
diff --git a/Framework/Lang/zh-cn.php b/Framework/Lang/zh-cn.php
new file mode 100644
index 00000000..355b0c71
--- /dev/null
+++ b/Framework/Lang/zh-cn.php
@@ -0,0 +1,51 @@
+
+// +----------------------------------------------------------------------
+
+/**
+ * ThinkPHP 简体中文语言包
+ */
+return array(
+ /* 核心语言变量 */
+ '_MODULE_NOT_EXIST_' => '无法加载模块',
+ '_CONTROLLER_NOT_EXIST_' => '无法加载控制器',
+ '_ERROR_ACTION_' => '非法操作',
+ '_LANGUAGE_NOT_LOAD_' => '无法加载语言包',
+ '_TEMPLATE_NOT_EXIST_' => '模板不存在',
+ '_MODULE_' => '模块',
+ '_ACTION_' => '操作',
+ '_MODEL_NOT_EXIST_' => '模型不存在或者没有定义',
+ '_VALID_ACCESS_' => '没有权限',
+ '_XML_TAG_ERROR_' => 'XML标签语法错误',
+ '_DATA_TYPE_INVALID_' => '非法数据对象!',
+ '_OPERATION_WRONG_' => '操作出现错误',
+ '_NOT_LOAD_DB_' => '无法加载数据库',
+ '_NO_DB_DRIVER_' => '无法加载数据库驱动',
+ '_NOT_SUPPORT_DB_' => '系统暂时不支持数据库',
+ '_NO_DB_CONFIG_' => '没有定义数据库配置',
+ '_NOT_SUPPORT_' => '系统不支持',
+ '_CACHE_TYPE_INVALID_' => '无法加载缓存类型',
+ '_FILE_NOT_WRITABLE_' => '目录(文件)不可写',
+ '_METHOD_NOT_EXIST_' => '方法不存在!',
+ '_CLASS_NOT_EXIST_' => '实例化一个不存在的类!',
+ '_CLASS_CONFLICT_' => '类名冲突',
+ '_TEMPLATE_ERROR_' => '模板引擎错误',
+ '_CACHE_WRITE_ERROR_' => '缓存文件写入失败!',
+ '_TAGLIB_NOT_EXIST_' => '标签库未定义',
+ '_OPERATION_FAIL_' => '操作失败!',
+ '_OPERATION_SUCCESS_' => '操作成功!',
+ '_SELECT_NOT_EXIST_' => '记录不存在!',
+ '_EXPRESS_ERROR_' => '表达式错误',
+ '_TOKEN_ERROR_' => '表单令牌错误',
+ '_RECORD_HAS_UPDATE_' => '记录已经更新',
+ '_NOT_ALLOW_PHP_' => '模板禁用PHP代码',
+ '_PARAM_ERROR_' => '参数错误或者未定义',
+ '_ERROR_QUERY_EXPRESS_' => '错误的查询条件',
+);
diff --git a/Framework/Lang/zh-tw.php b/Framework/Lang/zh-tw.php
new file mode 100644
index 00000000..fc928f62
--- /dev/null
+++ b/Framework/Lang/zh-tw.php
@@ -0,0 +1,51 @@
+
+// +----------------------------------------------------------------------
+
+/**
+ * ThinkPHP 繁体中文語言包
+ */
+return array(
+ /* 核心語言變數 */
+ '_MODULE_NOT_EXIST_' => '無法載入模組',
+ '_CONTROLLER_NOT_EXIST_' => '無法載入控制器',
+ '_ERROR_ACTION_' => '非法操作',
+ '_LANGUAGE_NOT_LOAD_' => '無法載入語言包',
+ '_TEMPLATE_NOT_EXIST_' => '模板不存在',
+ '_MODULE_' => '模組',
+ '_ACTION_' => '操作',
+ '_MODEL_NOT_EXIST_' => '模型不存在或者沒有定義',
+ '_VALID_ACCESS_' => '沒有權限',
+ '_XML_TAG_ERROR_' => 'XML標籤語法錯誤',
+ '_DATA_TYPE_INVALID_' => '非法資料物件!',
+ '_OPERATION_WRONG_' => '操作出現錯誤',
+ '_NOT_LOAD_DB_' => '無法載入資料庫',
+ '_NO_DB_DRIVER_' => '無法載入資料庫驅動',
+ '_NOT_SUPPORT_DB_' => '系統暫時不支援資料庫',
+ '_NO_DB_CONFIG_' => '沒有定義資料庫設定',
+ '_NOT_SUPPORT_' => '系統不支援',
+ '_CACHE_TYPE_INVALID_' => '無法載入快取類型',
+ '_FILE_NOT_WRITABLE_' => '目錄(檔案)不可寫',
+ '_METHOD_NOT_EXIST_' => '方法不存在!',
+ '_CLASS_NOT_EXIST_' => '實例化一個不存在的類別!',
+ '_CLASS_CONFLICT_' => '類別名稱衝突',
+ '_TEMPLATE_ERROR_' => '模板引擎錯誤',
+ '_CACHE_WRITE_ERROR_' => '快取檔案寫入失敗!',
+ '_TAGLIB_NOT_EXIST_' => '標籤庫未定義',
+ '_OPERATION_FAIL_' => '操作失敗!',
+ '_OPERATION_SUCCESS_' => '操作成功!',
+ '_SELECT_NOT_EXIST_' => '記錄不存在!',
+ '_EXPRESS_ERROR_' => '運算式錯誤',
+ '_TOKEN_ERROR_' => '表單權限錯誤',
+ '_RECORD_HAS_UPDATE_' => '記錄已經更新',
+ '_NOT_ALLOW_PHP_' => '模板禁用PHP代碼',
+ '_PARAM_ERROR_' => '參數錯誤或者未定義',
+ '_ERROR_QUERY_EXPRESS_' => '錯誤的查詢條件',
+);
diff --git a/Framework/Library/Behavior/AgentCheckBehavior.class.php b/Framework/Library/Behavior/AgentCheckBehavior.class.php
new file mode 100644
index 00000000..f663d7fb
--- /dev/null
+++ b/Framework/Library/Behavior/AgentCheckBehavior.class.php
@@ -0,0 +1,27 @@
+
+// +----------------------------------------------------------------------
+namespace Behavior;
+
+/**
+ * 行为扩展:代理检测
+ */
+class AgentCheckBehavior
+{
+ public function run(&$params)
+ {
+ // 代理访问检测
+ $limitProxyVisit = C('LIMIT_PROXY_VISIT', null, true);
+ if ($limitProxyVisit && ($_SERVER['HTTP_X_FORWARDED_FOR'] || $_SERVER['HTTP_VIA'] || $_SERVER['HTTP_PROXY_CONNECTION'] || $_SERVER['HTTP_USER_AGENT_VIA'])) {
+ // 禁止代理访问
+ exit('Access Denied');
+ }
+ }
+}
diff --git a/Framework/Library/Behavior/BorisBehavior.class.php b/Framework/Library/Behavior/BorisBehavior.class.php
new file mode 100644
index 00000000..840b914e
--- /dev/null
+++ b/Framework/Library/Behavior/BorisBehavior.class.php
@@ -0,0 +1,48 @@
+
+// +----------------------------------------------------------------------
+namespace Behavior;
+
+use Think\Think;
+
+/**
+ * Boris行为扩展
+ */
+class BorisBehavior
+{
+ public function run(&$params)
+ {
+ if (IS_CLI) {
+ if (!function_exists('pcntl_signal')) {
+ E("pcntl_signal not working.\nRepl mode based on Linux OS or PHP for OS X(http://php-osx.liip.ch/)\n");
+ }
+
+ Think::addMap(array(
+ 'Boris\Boris' => VENDOR_PATH . 'Boris/Boris.php',
+ 'Boris\Config' => VENDOR_PATH . 'Boris/Config.php',
+ 'Boris\CLIOptionsHandler' => VENDOR_PATH . 'Boris/CLIOptionsHandler.php',
+ 'Boris\ColoredInspector' => VENDOR_PATH . 'Boris/ColoredInspector.php',
+ 'Boris\DumpInspector' => VENDOR_PATH . 'Boris/DumpInspector.php',
+ 'Boris\EvalWorker' => VENDOR_PATH . 'Boris/EvalWorker.php',
+ 'Boris\ExportInspector' => VENDOR_PATH . 'Boris/ExportInspector.php',
+ 'Boris\Inspector' => VENDOR_PATH . 'Boris/Inspector.php',
+ 'Boris\ReadlineClient' => VENDOR_PATH . 'Boris/ReadlineClient.php',
+ 'Boris\ShallowParser' => VENDOR_PATH . 'Boris/ShallowParser.php',
+ ));
+ $boris = new \Boris\Boris(">>> ");
+ $config = new \Boris\Config();
+ $config->apply($boris, true);
+ $options = new \Boris\CLIOptionsHandler();
+ $options->handle($boris);
+ $boris->onStart(sprintf("echo 'REPL MODE FOR THINKPHP \nTHINKPHP_VERSION: %s, PHP_VERSION: %s, BORIS_VERSION: %s\n';", THINK_VERSION, PHP_VERSION, $boris::VERSION));
+ $boris->start();
+ }
+ }
+}
diff --git a/Framework/Library/Behavior/BrowserCheckBehavior.class.php b/Framework/Library/Behavior/BrowserCheckBehavior.class.php
new file mode 100644
index 00000000..f5554971
--- /dev/null
+++ b/Framework/Library/Behavior/BrowserCheckBehavior.class.php
@@ -0,0 +1,37 @@
+
+// +----------------------------------------------------------------------
+namespace Behavior;
+
+/**
+ * 浏览器防刷新检测
+ */
+class BrowserCheckBehavior
+{
+ public function run(&$params)
+ {
+ if ('GET' == $_SERVER['REQUEST_METHOD']) {
+ // 启用页面防刷新机制
+ $guid = md5($_SERVER['PHP_SELF']);
+ // 浏览器防刷新的时间间隔(秒) 默认为10
+ $refleshTime = C('LIMIT_REFLESH_TIMES', null, 10);
+ // 检查页面刷新间隔
+ if (cookie('_last_visit_time_' . $guid) && cookie('_last_visit_time_' . $guid) > time() - $refleshTime) {
+ // 页面刷新读取浏览器缓存
+ header('HTTP/1.1 304 Not Modified');
+ exit;
+ } else {
+ // 缓存当前地址访问时间
+ cookie('_last_visit_time_' . $guid, $_SERVER['REQUEST_TIME']);
+ //header('Last-Modified:'.(date('D,d M Y H:i:s',$_SERVER['REQUEST_TIME']-C('LIMIT_REFLESH_TIMES'))).' GMT');
+ }
+ }
+ }
+}
diff --git a/Framework/Library/Behavior/BuildLiteBehavior.class.php b/Framework/Library/Behavior/BuildLiteBehavior.class.php
new file mode 100644
index 00000000..145b4f74
--- /dev/null
+++ b/Framework/Library/Behavior/BuildLiteBehavior.class.php
@@ -0,0 +1,97 @@
+
+// +----------------------------------------------------------------------
+namespace Behavior;
+
+// 创建Lite运行文件
+// 可以替换框架入口文件运行
+// 建议绑定位置app_init
+use Think\Hook as Hook;
+class BuildLiteBehavior
+{
+ public function run(&$params)
+ {
+ if (!defined('BUILD_LITE_FILE') || BUILD_LITE_FILE == false) {
+ return;
+ }
+
+ $litefile = C('RUNTIME_LITE_FILE', null, RUNTIME_PATH . 'lite.php');
+ if (is_file($litefile)) {
+ return;
+ }
+
+ $defs = get_defined_constants(true);
+ $content = 'namespace {$GLOBALS[\'_beginTime\'] = microtime(TRUE);';
+ if (MEMORY_LIMIT_ON) {
+ $content .= '$GLOBALS[\'_startUseMems\'] = memory_get_usage();';
+ }
+
+ // 生成数组定义
+ unset($defs['user']['BUILD_LITE_FILE']);
+ $content .= $this->buildArrayDefine($defs['user']) . '}';
+
+ // 读取编译列表文件
+ $filelist = is_file(CONF_PATH . 'lite.php') ?
+ include CONF_PATH . 'lite.php' :
+ array(
+ THINK_PATH . 'Common/functions.php',
+ COMMON_PATH . 'Common/function.php',
+ CORE_PATH . 'Think' . EXT,
+ CORE_PATH . 'Hook' . EXT,
+ CORE_PATH . 'App' . EXT,
+ CORE_PATH . 'Dispatcher' . EXT,
+ CORE_PATH . 'Log' . EXT,
+ CORE_PATH . 'Log/Driver/File' . EXT,
+ CORE_PATH . 'Route' . EXT,
+ CORE_PATH . 'Controller' . EXT,
+ CORE_PATH . 'View' . EXT,
+ CORE_PATH . 'Storage' . EXT,
+ CORE_PATH . 'Storage/Driver/File' . EXT,
+ CORE_PATH . 'Exception' . EXT,
+ BEHAVIOR_PATH . 'ParseTemplateBehavior' . EXT,
+ BEHAVIOR_PATH . 'ContentReplaceBehavior' . EXT,
+ );
+
+ // 编译文件
+ foreach ($filelist as $file) {
+ if (is_file($file)) {
+ $content .= compile($file);
+ }
+ }
+
+ // 处理Think类的start方法
+ $content = preg_replace('/\$runtimefile = RUNTIME_PATH(.+?)(if\(APP_STATUS)/', '\2', $content, 1);
+ $content .= "\nnamespace { Think\Think::addMap(" . var_export(\Think\Think::getMap(), true) . ");";
+ $content .= "\nL(" . var_export(L(), true) . ");\nC(" . var_export(C(), true) . ');Think\Hook::import(' . var_export(\Think\Hook::get(), true) . ');Think\Think::start();}';
+
+ // 生成运行Lite文件
+ file_put_contents($litefile, strip_whitespace(' $val) {
+ $key = strtoupper($key);
+ $content .= 'defined(\'' . $key . '\') or ';
+ if (is_int($val) || is_float($val)) {
+ $content .= "define('" . $key . "'," . $val . ');';
+ } elseif (is_bool($val)) {
+ $val = ($val) ? 'true' : 'false';
+ $content .= "define('" . $key . "'," . $val . ');';
+ } elseif (is_string($val)) {
+ $content .= "define('" . $key . "','" . addslashes($val) . "');";
+ }
+ $content .= "\n";
+ }
+ return $content;
+ }
+}
diff --git a/Framework/Library/Behavior/CheckActionRouteBehavior.class.php b/Framework/Library/Behavior/CheckActionRouteBehavior.class.php
new file mode 100644
index 00000000..c78b7b76
--- /dev/null
+++ b/Framework/Library/Behavior/CheckActionRouteBehavior.class.php
@@ -0,0 +1,217 @@
+
+// +----------------------------------------------------------------------
+namespace Behavior;
+
+/**
+ * 系统行为扩展:操作路由检测
+ */
+class CheckActionRouteBehavior
+{
+
+ // 行为扩展的执行入口必须是run
+ public function run(&$config)
+ {
+ // 优先检测是否存在PATH_INFO
+ $regx = trim($_SERVER['PATH_INFO'], '/');
+ if (empty($regx)) {
+ return;
+ }
+
+ // 路由定义文件优先于config中的配置定义
+ // 路由处理
+ $routes = $config['routes'];
+ if (!empty($routes)) {
+ $depr = C('URL_PATHINFO_DEPR');
+ // 分隔符替换 确保路由定义使用统一的分隔符
+ $regx = str_replace($depr, '/', $regx);
+ $regx = substr_replace($regx, '', 0, strlen(__URL__));
+ foreach ($routes as $rule => $route) {
+ if (0 === strpos($rule, '/') && preg_match($rule, $regx, $matches)) {
+ // 正则路由
+ return C('ACTION_NAME', $this->parseRegex($matches, $route, $regx));
+ } else {
+ // 规则路由
+ $len1 = substr_count($regx, '/');
+ $len2 = substr_count($rule, '/');
+ if ($len1 >= $len2) {
+ if ('$' == substr($rule, -1, 1)) {
+// 完整匹配
+ if ($len1 != $len2) {
+ continue;
+ } else {
+ $rule = substr($rule, 0, -1);
+ }
+ }
+ $match = $this->checkUrlMatch($regx, $rule);
+ if ($match) {
+ return C('ACTION_NAME', $this->parseRule($rule, $route, $regx));
+ }
+
+ }
+ }
+ }
+ }
+ }
+
+ // 检测URL和规则路由是否匹配
+ private function checkUrlMatch($regx, $rule)
+ {
+ $m1 = explode('/', $regx);
+ $m2 = explode('/', $rule);
+ $match = true; // 是否匹配
+ foreach ($m2 as $key => $val) {
+ if (':' == substr($val, 0, 1)) {
+// 动态变量
+ if (strpos($val, '\\')) {
+ $type = substr($val, -1);
+ if ('d' == $type && !is_numeric($m1[$key])) {
+ $match = false;
+ break;
+ }
+ } elseif (strpos($val, '^')) {
+ $array = explode('|', substr(strstr($val, '^'), 1));
+ if (in_array($m1[$key], $array)) {
+ $match = false;
+ break;
+ }
+ }
+ } elseif (0 !== strcasecmp($val, $m1[$key])) {
+ $match = false;
+ break;
+ }
+ }
+ return $match;
+ }
+
+ // 解析规范的路由地址
+ // 地址格式 操作?参数1=值1&参数2=值2...
+ private function parseUrl($url)
+ {
+ $var = array();
+ if (false !== strpos($url, '?')) {
+ // 操作?参数1=值1&参数2=值2...
+ $info = parse_url($url);
+ $path = $info['path'];
+ parse_str($info['query'], $var);
+ } else {
+ // 操作
+ $path = $url;
+ }
+ $var[C('VAR_ACTION')] = $path;
+ return $var;
+ }
+
+ // 解析规则路由
+ // '路由规则'=>'操作?额外参数1=值1&额外参数2=值2...'
+ // '路由规则'=>array('操作','额外参数1=值1&额外参数2=值2...')
+ // '路由规则'=>'外部地址'
+ // '路由规则'=>array('外部地址','重定向代码')
+ // 路由规则中 :开头 表示动态变量
+ // 外部地址中可以用动态变量 采用 :1 :2 的方式
+ // 'news/:month/:day/:id'=>array('News/read?cate=1','status=1'),
+ // 'new/:id'=>array('/new.php?id=:1',301), 重定向
+ private function parseRule($rule, $route, $regx)
+ {
+ // 获取路由地址规则
+ $url = is_array($route) ? $route[0] : $route;
+ // 获取URL地址中的参数
+ $paths = explode('/', $regx);
+ // 解析路由规则
+ $matches = array();
+ $rule = explode('/', $rule);
+ foreach ($rule as $item) {
+ if (0 === strpos($item, ':')) {
+ // 动态变量获取
+ if ($pos = strpos($item, '^')) {
+ $var = substr($item, 1, $pos - 1);
+ } elseif (strpos($item, '\\')) {
+ $var = substr($item, 1, -2);
+ } else {
+ $var = substr($item, 1);
+ }
+ $matches[$var] = array_shift($paths);
+ } else {
+ // 过滤URL中的静态变量
+ array_shift($paths);
+ }
+ }
+ if (0 === strpos($url, '/') || 0 === strpos($url, 'http')) {
+ // 路由重定向跳转
+ if (strpos($url, ':')) { // 传递动态参数
+ $values = array_values($matches);
+ $url = preg_replace('/:(\d+)/e', '$values[\\1-1]', $url);
+ }
+ header("Location: $url", true, (is_array($route) && isset($route[1])) ? $route[1] : 301);
+ exit;
+ } else {
+ // 解析路由地址
+ $var = $this->parseUrl($url);
+ // 解析路由地址里面的动态参数
+ $values = array_values($matches);
+ foreach ($var as $key => $val) {
+ if (0 === strpos($val, ':')) {
+ $var[$key] = $values[substr($val, 1) - 1];
+ }
+ }
+ $var = array_merge($matches, $var);
+ // 解析剩余的URL参数
+ if ($paths) {
+ preg_replace('@(\w+)\/([^\/]+)@e', '$var[strtolower(\'\\1\')]=strip_tags(\'\\2\');', implode('/', $paths));
+ }
+ // 解析路由自动传入参数
+ if (is_array($route) && isset($route[1])) {
+ parse_str($route[1], $params);
+ $var = array_merge($var, $params);
+ }
+ $action = $var[C('VAR_ACTION')];
+ unset($var[C('VAR_ACTION')]);
+ $_GET = array_merge($var, $_GET);
+ return $action;
+ }
+ }
+
+ // 解析正则路由
+ // '路由正则'=>'[分组/模块/操作]?参数1=值1&参数2=值2...'
+ // '路由正则'=>array('[分组/模块/操作]?参数1=值1&参数2=值2...','额外参数1=值1&额外参数2=值2...')
+ // '路由正则'=>'外部地址'
+ // '路由正则'=>array('外部地址','重定向代码')
+ // 参数值和外部地址中可以用动态变量 采用 :1 :2 的方式
+ // '/new\/(\d+)\/(\d+)/'=>array('News/read?id=:1&page=:2&cate=1','status=1'),
+ // '/new\/(\d+)/'=>array('/new.php?id=:1&page=:2&status=1','301'), 重定向
+ private function parseRegex($matches, $route, $regx)
+ {
+ // 获取路由地址规则
+ $url = is_array($route) ? $route[0] : $route;
+ $url = preg_replace('/:(\d+)/e', '$matches[\\1]', $url);
+ if (0 === strpos($url, '/') || 0 === strpos($url, 'http')) {
+ // 路由重定向跳转
+ header("Location: $url", true, (is_array($route) && isset($route[1])) ? $route[1] : 301);
+ exit;
+ } else {
+ // 解析路由地址
+ $var = $this->parseUrl($url);
+ // 解析剩余的URL参数
+ $regx = substr_replace($regx, '', 0, strlen($matches[0]));
+ if ($regx) {
+ preg_replace('@(\w+)\/([^,\/]+)@e', '$var[strtolower(\'\\1\')]=strip_tags(\'\\2\');', $regx);
+ }
+ // 解析路由自动传入参数
+ if (is_array($route) && isset($route[1])) {
+ parse_str($route[1], $params);
+ $var = array_merge($var, $params);
+ }
+ $action = $var[C('VAR_ACTION')];
+ unset($var[C('VAR_ACTION')]);
+ $_GET = array_merge($var, $_GET);
+ }
+ return $action;
+ }
+}
diff --git a/Framework/Library/Behavior/CheckLangBehavior.class.php b/Framework/Library/Behavior/CheckLangBehavior.class.php
new file mode 100644
index 00000000..2fd22a53
--- /dev/null
+++ b/Framework/Library/Behavior/CheckLangBehavior.class.php
@@ -0,0 +1,89 @@
+
+// +----------------------------------------------------------------------
+namespace Behavior;
+
+/**
+ * 语言检测 并自动加载语言包
+ */
+class CheckLangBehavior
+{
+
+ // 行为扩展的执行入口必须是run
+ public function run(&$params)
+ {
+ // 检测语言
+ $this->checkLanguage();
+ }
+
+ /**
+ * 语言检查
+ * 检查浏览器支持语言,并自动加载语言包
+ * @access private
+ * @return void
+ */
+ private function checkLanguage()
+ {
+ // 不开启语言包功能,仅仅加载框架语言文件直接返回
+ if (!C('LANG_SWITCH_ON', null, false)) {
+ return;
+ }
+ $langSet = C('DEFAULT_LANG');
+ $varLang = C('VAR_LANGUAGE', null, 'l');
+ $langList = C('LANG_LIST', null, 'zh-cn');
+ // 启用了语言包功能
+ // 根据是否启用自动侦测设置获取语言选择
+ if (C('LANG_AUTO_DETECT', null, true)) {
+ if (isset($_GET[$varLang])) {
+ $langSet = $_GET[$varLang]; // url中设置了语言变量
+ cookie('think_language', $langSet, 3600);
+ } elseif (cookie('think_language')) {
+// 获取上次用户的选择
+ $langSet = cookie('think_language');
+ } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
+// 自动侦测浏览器语言
+ preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches);
+ $langSet = $matches[1];
+ cookie('think_language', $langSet, 3600);
+ }
+ if (false === stripos($langList, $langSet)) {
+ // 非法语言参数
+ $langSet = C('DEFAULT_LANG');
+ }
+ }
+ // 定义当前语言
+ define('LANG_SET', strtolower($langSet));
+
+ // 读取框架语言包
+ $file = THINK_PATH . 'Lang/' . LANG_SET . '.php';
+ if (LANG_SET != C('DEFAULT_LANG') && is_file($file)) {
+ L(include $file);
+ }
+
+ // 读取应用公共语言包
+ $file = LANG_PATH . LANG_SET . '.php';
+ if (is_file($file)) {
+ L(include $file);
+ }
+
+ // 读取模块语言包
+ $file = MODULE_PATH . 'Lang/' . LANG_SET . '.php';
+ if (is_file($file)) {
+ L(include $file);
+ }
+
+ // 读取当前控制器语言包
+ $file = MODULE_PATH . 'Lang/' . LANG_SET . '/' . strtolower(CONTROLLER_NAME) . '.php';
+ if (is_file($file)) {
+ L(include $file);
+ }
+
+ }
+}
diff --git a/Framework/Library/Behavior/ChromeShowPageTraceBehavior.class.php b/Framework/Library/Behavior/ChromeShowPageTraceBehavior.class.php
new file mode 100644
index 00000000..60415a6d
--- /dev/null
+++ b/Framework/Library/Behavior/ChromeShowPageTraceBehavior.class.php
@@ -0,0 +1,620 @@
+
+// +----------------------------------------------------------------------
+// $Id$
+
+/**
+ * 将Trace信息输出到chrome浏览器的控制器,从而不影响ajax效果和页面的布局。
+ * 使用前,你需要先安装 chrome log 这个插件: http://craig.is/writing/chrome-logger。
+ * 定义应用的tags.php文件 Application/Common/Conf/tags.php,
+ *
+ * array(
+ * 'Behavior\ChromeShowPageTrace'
+ * )
+ * );
+ *
+ * 如果trace信息没有正常输出,请查看您的日志。
+ * 这是通过http headers和chrome通信,所以要保证在输出trace信息之前不能有
+ * headers输出,你可以在入口文件第一行加入代码 ob_start(); 或者配置output_buffering
+ *
+ */
+namespace Behavior;
+
+use Behavior\ChromePhp as ChromePhp;
+use Think\Log;
+
+/**
+ * 系统行为扩展 页面Trace显示输出
+ */
+class ChromeShowPageTraceBehavior
+{
+
+ protected $tracePageTabs = array('BASE' => '基本', 'FILE' => '文件', 'INFO' => '流程', 'ERR|NOTIC' => '错误', 'SQL' => 'SQL', 'DEBUG' => '调试');
+
+ // 行为扩展的执行入口必须是run
+ public function run(&$params)
+ {
+ if (C('SHOW_PAGE_TRACE')) {
+ $this->showTrace();
+ }
+
+ }
+
+ /**
+ * 显示页面Trace信息
+ * @access private
+ */
+ private function showTrace()
+ {
+ // 系统默认显示信息
+ $files = get_included_files();
+ $info = array();
+ foreach ($files as $key => $file) {
+ $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )';
+ }
+ $trace = array();
+ $base = array(
+ '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . __SELF__,
+ '运行时间' => $this->showTime(),
+ '吞吐率' => number_format(1 / G('beginTime', 'viewEndTime'), 2) . 'req/s',
+ '内存开销' => MEMORY_LIMIT_ON ? number_format((memory_get_usage() - $GLOBALS['_startUseMems']) / 1024, 2) . ' kb' : '不支持',
+ '查询信息' => N('db_query') . ' queries ' . N('db_write') . ' writes ',
+ '文件加载' => count(get_included_files()),
+ '缓存信息' => N('cache_read') . ' gets ' . N('cache_write') . ' writes ',
+ '配置加载' => count(c()),
+ '会话信息' => 'SESSION_ID=' . session_id(),
+ );
+ // 读取应用定义的Trace文件
+ $traceFile = COMMON_PATH . 'Conf/trace.php';
+ if (is_file($traceFile)) {
+ $base = array_merge($base, include $traceFile);
+ }
+
+ $debug = trace();
+ $tabs = C('TRACE_PAGE_TABS', null, $this->tracePageTabs);
+ foreach ($tabs as $name => $title) {
+ switch (strtoupper($name)) {
+ case 'BASE': // 基本信息
+ $trace[$title] = $base;
+ break;
+ case 'FILE': // 文件信息
+ $trace[$title] = $info;
+ break;
+ default: // 调试信息
+ $name = strtoupper($name);
+ if (strpos($name, '|')) {
+// 多组信息
+ $array = explode('|', $name);
+ $result = array();
+ foreach ($array as $name) {
+ $result += isset($debug[$name]) ? $debug[$name] : array();
+ }
+ $trace[$title] = $result;
+ } else {
+ $trace[$title] = isset($debug[$name]) ? $debug[$name] : '';
+ }
+ }
+ }
+ chromeDebug('TRACE信息:' . __SELF__, 'group');
+ //输出日志
+ foreach ($trace as $title => $log) {
+ '错误' == $title ? chromeDebug($title, 'group') : chromeDebug($title, 'groupCollapsed');
+ foreach ($log as $i => $logstr) {
+ chromeDebug($i . '.' . $logstr, 'log');
+ }
+ chromeDebug('', 'groupEnd');
+ }
+ chromeDebug('', 'groupEnd');
+ if ($save = C('PAGE_TRACE_SAVE')) {
+ // 保存页面Trace日志
+ if (is_array($save)) { // 选择选项卡保存
+ $tabs = C('TRACE_PAGE_TABS', null, $this->tracePageTabs);
+ $array = array();
+ foreach ($save as $tab) {
+ $array[] = $tabs[$tab];
+ }
+ }
+ $content = date('[ c ]') . ' ' . get_client_ip() . ' ' . $_SERVER['REQUEST_URI'] . "\r\n";
+ foreach ($trace as $key => $val) {
+ if (!isset($array) || in_array($key, $array)) {
+ $content .= '[ ' . $key . " ]\r\n";
+ if (is_array($val)) {
+ foreach ($val as $k => $v) {
+ $content .= (!is_numeric($k) ? $k . ':' : '') . print_r($v, true) . "\r\n";
+ }
+ } else {
+ $content .= print_r($val, true) . "\r\n";
+ }
+ $content .= "\r\n";
+ }
+ }
+ error_log(str_replace(' ', "\r\n", $content), 3, LOG_PATH . date('y_m_d') . '_trace.log');
+ }
+ unset($files, $info, $base);
+ }
+
+ /**
+ * 获取运行时间
+ */
+ private function showTime()
+ {
+ // 显示运行时间
+ G('beginTime', $GLOBALS['_beginTime']);
+ G('viewEndTime');
+ // 显示详细运行时间
+ return G('beginTime', 'viewEndTime') . 's ( Load:' . G('beginTime', 'loadTime') . 's Init:' . G('loadTime', 'initTime') . 's Exec:' . G('initTime', 'viewStartTime') . 's Template:' . G('viewStartTime', 'viewEndTime') . 's )';
+ }
+}
+if (!function_exists('chrome_debug')) {
+//ChromePhp 输出trace的函数
+ function chromeDebug($msg, $type = 'trace', $trace_level = 1)
+ {
+ if ('trace' == $type) {
+ ChromePhp::groupCollapsed($msg);
+ $traces = debug_backtrace(false);
+ $traces = array_reverse($traces);
+ $max = count($traces) - $trace_level;
+ for ($i = 0; $i < $max; $i++) {
+ $trace = $traces[$i];
+ $fun = isset($trace['class']) ? $trace['class'] . '::' . $trace['function'] : $trace['function'];
+ $file = isset($trace['file']) ? $trace['file'] : 'unknown file';
+ $line = isset($trace['line']) ? $trace['line'] : 'unknown line';
+ $trace_msg = '#' . $i . ' ' . $fun . ' called at [' . $file . ':' . $line . ']';
+ if (!empty($trace['args'])) {
+ ChromePhp::groupCollapsed($trace_msg);
+ ChromePhp::log($trace['args']);
+ ChromePhp::groupEnd();
+ } else {
+ ChromePhp::log($trace_msg);
+ }
+ }
+ ChromePhp::groupEnd();
+ } else {
+ if (method_exists('Behavior\ChromePhp', $type)) {
+ //支持type trace,warn,log,error,group, groupCollapsed, groupEnd等
+ call_user_func(array('Behavior\ChromePhp', $type), $msg);
+ } else {
+ //如果type不为trace,warn,log等,则为log的标签
+ call_user_func_array(array('Behavior\ChromePhp', 'log'), func_get_args());
+ }
+ }
+ }
+
+/**
+ * Server Side Chrome PHP debugger class
+ *
+ * @package ChromePhp
+ * @author Craig Campbell
+ */
+ class ChromePhp
+ {
+ /**
+ * @var string
+ */
+ const VERSION = '4.1.0';
+
+ /**
+ * @var string
+ */
+ const HEADER_NAME = 'X-ChromeLogger-Data';
+
+ /**
+ * @var string
+ */
+ const BACKTRACE_LEVEL = 'backtrace_level';
+
+ /**
+ * @var string
+ */
+ const LOG = 'log';
+
+ /**
+ * @var string
+ */
+ const WARN = 'warn';
+
+ /**
+ * @var string
+ */
+ const ERROR = 'error';
+
+ /**
+ * @var string
+ */
+ const GROUP = 'group';
+
+ /**
+ * @var string
+ */
+ const INFO = 'info';
+
+ /**
+ * @var string
+ */
+ const GROUP_END = 'groupEnd';
+
+ /**
+ * @var string
+ */
+ const GROUP_COLLAPSED = 'groupCollapsed';
+
+ /**
+ * @var string
+ */
+ const TABLE = 'table';
+
+ /**
+ * @var string
+ */
+ protected $_php_version;
+
+ /**
+ * @var int
+ */
+ protected $_timestamp;
+
+ /**
+ * @var array
+ */
+ protected $_json = array(
+ 'version' => self::VERSION,
+ 'columns' => array('log', 'backtrace', 'type'),
+ 'rows' => array(),
+ );
+
+ /**
+ * @var array
+ */
+ protected $_backtraces = array();
+
+ /**
+ * @var bool
+ */
+ protected $_error_triggered = false;
+
+ /**
+ * @var array
+ */
+ protected $_settings = array(
+ self::BACKTRACE_LEVEL => 1,
+ );
+
+ /**
+ * @var ChromePhp
+ */
+ protected static $_instance;
+
+ /**
+ * Prevent recursion when working with objects referring to each other
+ *
+ * @var array
+ */
+ protected $_processed = array();
+
+ /**
+ * constructor
+ */
+ private function __construct()
+ {
+ $this->_php_version = phpversion();
+ $this->_timestamp = $this->_php_version >= 5.1 ? $_SERVER['REQUEST_TIME'] : time();
+ $this->_json['request_uri'] = $_SERVER['REQUEST_URI'];
+ }
+
+ /**
+ * gets instance of this class
+ *
+ * @return ChromePhp
+ */
+ public static function getInstance()
+ {
+ if (null === self::$_instance) {
+ self::$_instance = new self();
+ }
+ return self::$_instance;
+ }
+
+ /**
+ * logs a variable to the console
+ *
+ * @param mixed $data,... unlimited OPTIONAL number of additional logs [...]
+ * @return void
+ */
+ public static function log()
+ {
+ $args = func_get_args();
+ return self::_log('', $args);
+ }
+
+ /**
+ * logs a warning to the console
+ *
+ * @param mixed $data,... unlimited OPTIONAL number of additional logs [...]
+ * @return void
+ */
+ public static function warn()
+ {
+ $args = func_get_args();
+ return self::_log(self::WARN, $args);
+ }
+
+ /**
+ * logs an error to the console
+ *
+ * @param mixed $data,... unlimited OPTIONAL number of additional logs [...]
+ * @return void
+ */
+ public static function error()
+ {
+ $args = func_get_args();
+ return self::_log(self::ERROR, $args);
+ }
+
+ /**
+ * sends a group log
+ *
+ * @param string value
+ */
+ public static function group()
+ {
+ $args = func_get_args();
+ return self::_log(self::GROUP, $args);
+ }
+
+ /**
+ * sends an info log
+ *
+ * @param mixed $data,... unlimited OPTIONAL number of additional logs [...]
+ * @return void
+ */
+ public static function info()
+ {
+ $args = func_get_args();
+ return self::_log(self::INFO, $args);
+ }
+
+ /**
+ * sends a collapsed group log
+ *
+ * @param string value
+ */
+ public static function groupCollapsed()
+ {
+ $args = func_get_args();
+ return self::_log(self::GROUP_COLLAPSED, $args);
+ }
+
+ /**
+ * ends a group log
+ *
+ * @param string value
+ */
+ public static function groupEnd()
+ {
+ $args = func_get_args();
+ return self::_log(self::GROUP_END, $args);
+ }
+
+ /**
+ * sends a table log
+ *
+ * @param string value
+ */
+ public static function table()
+ {
+ $args = func_get_args();
+ return self::_log(self::TABLE, $args);
+ }
+
+ /**
+ * internal logging call
+ *
+ * @param string $type
+ * @return void
+ */
+ protected static function _log($type, array $args)
+ {
+ // nothing passed in, don't do anything
+ if (count($args) == 0 && self::GROUP_END != $type) {
+ return;
+ }
+
+ $logger = self::getInstance();
+
+ $logger->_processed = array();
+
+ $logs = array();
+ foreach ($args as $arg) {
+ $logs[] = $logger->_convert($arg);
+ }
+
+ $backtrace = debug_backtrace(false);
+ $level = $logger->getSetting(self::BACKTRACE_LEVEL);
+
+ $backtrace_message = 'unknown';
+ if (isset($backtrace[$level]['file']) && isset($backtrace[$level]['line'])) {
+ $backtrace_message = $backtrace[$level]['file'] . ' : ' . $backtrace[$level]['line'];
+ }
+
+ $logger->_addRow($logs, $backtrace_message, $type);
+ }
+
+ /**
+ * converts an object to a better format for logging
+ *
+ * @param Object
+ * @return array
+ */
+ protected function _convert($object)
+ {
+ // if this isn't an object then just return it
+ if (!is_object($object)) {
+ return $object;
+ }
+
+ //Mark this object as processed so we don't convert it twice and it
+ //Also avoid recursion when objects refer to each other
+ $this->_processed[] = $object;
+
+ $object_as_array = array();
+
+ // first add the class name
+ $object_as_array['___class_name'] = get_class($object);
+
+ // loop through object vars
+ $object_vars = get_object_vars($object);
+ foreach ($object_vars as $key => $value) {
+
+ // same instance as parent object
+ if ($value === $object || in_array($value, $this->_processed, true)) {
+ $value = 'recursion - parent object [' . get_class($value) . ']';
+ }
+ $object_as_array[$key] = $this->_convert($value);
+ }
+
+ $reflection = new ReflectionClass($object);
+
+ // loop through the properties and add those
+ foreach ($reflection->getProperties() as $property) {
+
+ // if one of these properties was already added above then ignore it
+ if (array_key_exists($property->getName(), $object_vars)) {
+ continue;
+ }
+ $type = $this->_getPropertyKey($property);
+
+ if ($this->_php_version >= 5.3) {
+ $property->setAccessible(true);
+ }
+
+ try {
+ $value = $property->getValue($object);
+ } catch (ReflectionException $e) {
+ $value = 'only PHP 5.3 can access private/protected properties';
+ }
+
+ // same instance as parent object
+ if ($value === $object || in_array($value, $this->_processed, true)) {
+ $value = 'recursion - parent object [' . get_class($value) . ']';
+ }
+
+ $object_as_array[$type] = $this->_convert($value);
+ }
+ return $object_as_array;
+ }
+
+ /**
+ * takes a reflection property and returns a nicely formatted key of the property name
+ *
+ * @param ReflectionProperty
+ * @return string
+ */
+ protected function _getPropertyKey(ReflectionProperty $property)
+ {
+ $static = $property->isStatic() ? ' static' : '';
+ if ($property->isPublic()) {
+ return 'public' . $static . ' ' . $property->getName();
+ }
+
+ if ($property->isProtected()) {
+ return 'protected' . $static . ' ' . $property->getName();
+ }
+
+ if ($property->isPrivate()) {
+ return 'private' . $static . ' ' . $property->getName();
+ }
+ }
+
+ /**
+ * adds a value to the data array
+ *
+ * @var mixed
+ * @return void
+ */
+ protected function _addRow(array $logs, $backtrace, $type)
+ {
+ // if this is logged on the same line for example in a loop, set it to null to save space
+ if (in_array($backtrace, $this->_backtraces)) {
+ $backtrace = null;
+ }
+
+ // for group, groupEnd, and groupCollapsed
+ // take out the backtrace since it is not useful
+ if (self::GROUP == $type || self::GROUP_END == $type || self::GROUP_COLLAPSED == $type) {
+ $backtrace = null;
+ }
+
+ if (null !== $backtrace) {
+ $this->_backtraces[] = $backtrace;
+ }
+
+ $row = array($logs, $backtrace, $type);
+
+ $this->_json['rows'][] = $row;
+ $this->_writeHeader($this->_json);
+ }
+
+ protected function _writeHeader($data)
+ {
+ header(self::HEADER_NAME . ': ' . $this->_encode($data));
+ }
+
+ /**
+ * encodes the data to be sent along with the request
+ *
+ * @param array $data
+ * @return string
+ */
+ protected function _encode($data)
+ {
+ return base64_encode(utf8_encode(json_encode($data)));
+ }
+
+ /**
+ * adds a setting
+ *
+ * @param string key
+ * @param mixed value
+ * @return void
+ */
+ public function addSetting($key, $value)
+ {
+ $this->_settings[$key] = $value;
+ }
+
+ /**
+ * add ability to set multiple settings in one call
+ *
+ * @param array $settings
+ * @return void
+ */
+ public function addSettings(array $settings)
+ {
+ foreach ($settings as $key => $value) {
+ $this->addSetting($key, $value);
+ }
+ }
+
+ /**
+ * gets a setting
+ *
+ * @param string key
+ * @return mixed
+ */
+ public function getSetting($key)
+ {
+ if (!isset($this->_settings[$key])) {
+ return null;
+ }
+ return $this->_settings[$key];
+ }
+ }
+}
diff --git a/Framework/Library/Behavior/ContentReplaceBehavior.class.php b/Framework/Library/Behavior/ContentReplaceBehavior.class.php
new file mode 100644
index 00000000..8a30496f
--- /dev/null
+++ b/Framework/Library/Behavior/ContentReplaceBehavior.class.php
@@ -0,0 +1,53 @@
+
+// +----------------------------------------------------------------------
+namespace Behavior;
+
+/**
+ * 系统行为扩展:模板内容输出替换
+ */
+class ContentReplaceBehavior
+{
+
+ // 行为扩展的执行入口必须是run
+ public function run(&$content)
+ {
+ $content = $this->templateContentReplace($content);
+ }
+
+ /**
+ * 模板内容替换
+ * @access protected
+ * @param string $content 模板内容
+ * @return string
+ */
+ protected function templateContentReplace($content)
+ {
+ // 系统默认的特殊变量替换
+ $replace = array(
+ '__ROOT__' => __ROOT__, // 当前网站地址
+ '__APP__' => __APP__, // 当前应用地址
+ '__MODULE__' => __MODULE__,
+ '__ACTION__' => __ACTION__, // 当前操作地址
+ '__SELF__' => htmlentities(__SELF__), // 当前页面地址
+ '__CONTROLLER__' => __CONTROLLER__,
+ '__URL__' => __CONTROLLER__,
+ '__PUBLIC__' => __ROOT__ . '/Public', // 站点公共目录
+ );
+ // 允许用户自定义模板的字符串替换
+ if (is_array(C('TMPL_PARSE_STRING'))) {
+ $replace = array_merge($replace, C('TMPL_PARSE_STRING'));
+ }
+
+ $content = str_replace(array_keys($replace), array_values($replace), $content);
+ return $content;
+ }
+
+}
diff --git a/Framework/Library/Behavior/CronRunBehavior.class.php b/Framework/Library/Behavior/CronRunBehavior.class.php
new file mode 100644
index 00000000..74dd5013
--- /dev/null
+++ b/Framework/Library/Behavior/CronRunBehavior.class.php
@@ -0,0 +1,70 @@
+
+// +----------------------------------------------------------------------
+namespace Behavior;
+
+/**
+ * 自动执行任务
+ */
+use Think\Log as Log;
+class CronRunBehavior
+{
+
+ public function run(&$params)
+ {
+ // 锁定自动执行
+ $lockfile = RUNTIME_PATH . 'cron.lock';
+ if (is_writable($lockfile) && filemtime($lockfile) > $_SERVER['REQUEST_TIME'] - C('CRON_MAX_TIME', null, 60)) {
+ return;
+ } else {
+ touch($lockfile);
+ }
+ set_time_limit(1000);
+ ignore_user_abort(true);
+
+ // 载入cron配置文件
+ // 格式 return array(
+ // 'cronname'=>array('filename',intervals,nextruntime),...
+ // );
+ if (is_file(RUNTIME_PATH . '~crons.php')) {
+ $crons = include RUNTIME_PATH . '~crons.php';
+ } elseif (is_file(COMMON_PATH . 'Conf/crons.php')) {
+ $crons = include COMMON_PATH . 'Conf/crons.php';
+ }
+ if (isset($crons) && is_array($crons)) {
+ $update = false;
+ $log = array();
+ foreach ($crons as $key => $cron) {
+ if (empty($cron[2]) || $_SERVER['REQUEST_TIME'] >= $cron[2]) {
+ // 到达时间 执行cron文件
+ G('cronStart');
+ include COMMON_PATH . 'Cron/' . $cron[0] . '.php';
+ G('cronEnd');
+ $_useTime = G('cronStart', 'cronEnd', 6);
+ // 更新cron记录
+ $cron[2] = $_SERVER['REQUEST_TIME'] + $cron[1];
+ $crons[$key] = $cron;
+ $log[] = "Cron:$key Runat " . date('Y-m-d H:i:s') . " Use $_useTime s\n";
+ $update = true;
+ }
+ }
+ if ($update) {
+ // 记录Cron执行日志
+ \Think\Log::write(implode('', $log));
+ // 更新cron文件
+ $content = "";
+ file_put_contents(RUNTIME_PATH . '~crons.php', $content);
+ }
+ }
+ // 解除锁定
+ unlink($lockfile);
+ return;
+ }
+}
diff --git a/Framework/Library/Behavior/FireShowPageTraceBehavior.class.php b/Framework/Library/Behavior/FireShowPageTraceBehavior.class.php
new file mode 100644
index 00000000..0fbd2c4d
--- /dev/null
+++ b/Framework/Library/Behavior/FireShowPageTraceBehavior.class.php
@@ -0,0 +1,2118 @@
+
+// +----------------------------------------------------------------------
+// $Id$
+
+/**
+ * 将Trace信息输出到火狐的firebug,从而不影响ajax效果和页面的布局。
+ * 使用前,你需要先在火狐浏览器上安装firebug和firePHP两个插件。
+ * 定义应用的tags.php文件,
+ *
+ * array(
+ * 'FireShowPageTrace'
+ * )
+ * );
+ *
+ * 再将此文件放到应用的Behavior文件夹中即可
+ * 如果trace信息没有正常输出,请查看您的日志。
+ * firePHP,是通过http headers和firebug通讯的,所以要保证在输出trace信息之前不能有
+ * headers输出,你可以在入口文件第一行加入代码 ob_start(); 或者配置output_buffering
+ *
+ */
+namespace Behavior;
+
+/**
+ * 系统行为扩展 页面Trace显示输出
+ */
+use Behavior\FirePHP as FirePHP;
+use Think\Exception as Exception;
+class FireShowPageTraceBehavior
+{
+ protected $tracePagTabs = array('BASE' => '基本', 'FILE' => '文件', 'INFO' => '流程', 'ERR|NOTIC' => '错误', 'SQL' => 'SQL', 'DEBUG' => '调试');
+
+ // 行为扩展的执行入口必须是run
+ public function run(&$params)
+ {
+ if (C('FIRE_SHOW_PAGE_TRACE', null, true)) {
+ $this->showTrace();
+ }
+
+ }
+
+ /**
+ * 显示页面Trace信息
+ * @access private
+ */
+ private function showTrace()
+ {
+ // 系统默认显示信息
+ $files = get_included_files();
+ $info = array();
+ foreach ($files as $key => $file) {
+ $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )';
+ }
+ $trace = array();
+ $base = array(
+ '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . __SELF__,
+ '运行时间' => $this->showTime(),
+ '内存开销' => MEMORY_LIMIT_ON ? number_format((memory_get_usage() - $GLOBALS['_startUseMems']) / 1024, 2) . ' kb' : '不支持',
+ '查询信息' => N('db_query') . ' queries ' . N('db_write') . ' writes ',
+ '文件加载' => count(get_included_files()),
+ '缓存信息' => N('cache_read') . ' gets ' . N('cache_write') . ' writes ',
+ '配置加载' => count(c()),
+ '会话信息' => 'SESSION_ID=' . session_id(),
+ );
+ // 读取应用定义的Trace文件
+ $traceFile = CONF_PATH . 'trace.php';
+ if (is_file($traceFile)) {
+ $base = array_merge($base, include $traceFile);
+ }
+ $debug = trace();
+ $tabs = C('TRACE_PAGE_TABS', null, $this->tracePagTabs);
+ foreach ($tabs as $name => $title) {
+ switch (strtoupper($name)) {
+ case 'BASE': // 基本信息
+ $trace[$title] = $base;
+ break;
+ case 'FILE': // 文件信息
+ $trace[$title] = $info;
+ break;
+ default: // 调试信息
+ if (strpos($name, '|')) {
+// 多组信息
+ $array = explode('|', $name);
+ $result = array();
+ foreach ($array as $name) {
+ $result += isset($debug[$name]) ? $debug[$name] : array();
+ }
+ $trace[$title] = $result;
+ } else {
+ $trace[$title] = isset($debug[$name]) ? $debug[$name] : '';
+ }
+ }
+ }
+ foreach ($trace as $key => $val) {
+ if (!is_array($val) && empty($val)) {
+ $val = array();
+ }
+
+ if (is_array($val)) {
+ $fire = array(
+ array('', ''),
+ );
+ foreach ($val as $k => $v) {
+ $fire[] = array($k, $v);
+ }
+ fb(array($key, $fire), FirePHP::TABLE);
+ } else {
+ fb($val, $key);
+ }
+ }
+ unset($files, $info, $log, $base);
+ }
+
+ /**
+ * 获取运行时间
+ */
+ private function showTime()
+ {
+ // 显示运行时间
+ G('beginTime', $GLOBALS['_beginTime']);
+ G('viewEndTime');
+ // 显示详细运行时间
+ return G('beginTime', 'viewEndTime') . 's ( Load:' . G('beginTime', 'loadTime') . 's Init:' . G('loadTime', 'initTime') . 's Exec:' . G('initTime', 'viewStartTime') . 's Template:' . G('viewStartTime', 'viewEndTime') . 's )';
+ }
+
+}
+
+function fb()
+{
+ $instance = FirePHP::getInstance(true);
+
+ $args = func_get_args();
+ return call_user_func_array(array($instance, 'fb'), $args);
+}
+
+class FB
+{
+ /**
+ * Enable and disable logging to Firebug
+ *
+ * @see FirePHP->setEnabled()
+ * @param boolean $Enabled TRUE to enable, FALSE to disable
+ * @return void
+ */
+ public static function setEnabled($Enabled)
+ {
+ $instance = FirePHP::getInstance(true);
+ $instance->setEnabled($Enabled);
+ }
+
+ /**
+ * Check if logging is enabled
+ *
+ * @see FirePHP->getEnabled()
+ * @return boolean TRUE if enabled
+ */
+ public static function getEnabled()
+ {
+ $instance = FirePHP::getInstance(true);
+ return $instance->getEnabled();
+ }
+
+ /**
+ * Specify a filter to be used when encoding an object
+ *
+ * Filters are used to exclude object members.
+ *
+ * @see FirePHP->setObjectFilter()
+ * @param string $Class The class name of the object
+ * @param array $Filter An array or members to exclude
+ * @return void
+ */
+ public static function setObjectFilter($Class, $Filter)
+ {
+ $instance = FirePHP::getInstance(true);
+ $instance->setObjectFilter($Class, $Filter);
+ }
+
+ /**
+ * Set some options for the library
+ *
+ * @see FirePHP->setOptions()
+ * @param array $Options The options to be set
+ * @return void
+ */
+ public static function setOptions($Options)
+ {
+ $instance = FirePHP::getInstance(true);
+ $instance->setOptions($Options);
+ }
+
+ /**
+ * Get options for the library
+ *
+ * @see FirePHP->getOptions()
+ * @return array The options
+ */
+ public static function getOptions()
+ {
+ $instance = FirePHP::getInstance(true);
+ return $instance->getOptions();
+ }
+
+ /**
+ * Log object to firebug
+ *
+ * @see http://www.firephp.org/Wiki/Reference/Fb
+ * @param mixed $Object
+ * @return true
+ * @throws Exception
+ */
+ public static function send()
+ {
+ $instance = FirePHP::getInstance(true);
+ $args = func_get_args();
+ return call_user_func_array(array($instance, 'fb'), $args);
+ }
+
+ /**
+ * Start a group for following messages
+ *
+ * Options:
+ * Collapsed: [true|false]
+ * Color: [#RRGGBB|ColorName]
+ *
+ * @param string $Name
+ * @param array $Options OPTIONAL Instructions on how to log the group
+ * @return true
+ */
+ public static function group($Name, $Options = null)
+ {
+ $instance = FirePHP::getInstance(true);
+ return $instance->group($Name, $Options);
+ }
+
+ /**
+ * Ends a group you have started before
+ *
+ * @return true
+ * @throws Exception
+ */
+ public static function groupEnd()
+ {
+ return self::send(null, null, FirePHP::GROUP_END);
+ }
+
+ /**
+ * Log object with label to firebug console
+ *
+ * @see FirePHP::LOG
+ * @param mixes $Object
+ * @param string $Label
+ * @return true
+ * @throws Exception
+ */
+ public static function log($Object, $Label = null)
+ {
+ return self::send($Object, $Label, FirePHP::LOG);
+ }
+
+ /**
+ * Log object with label to firebug console
+ *
+ * @see FirePHP::INFO
+ * @param mixes $Object
+ * @param string $Label
+ * @return true
+ * @throws Exception
+ */
+ public static function info($Object, $Label = null)
+ {
+ return self::send($Object, $Label, FirePHP::INFO);
+ }
+
+ /**
+ * Log object with label to firebug console
+ *
+ * @see FirePHP::WARN
+ * @param mixes $Object
+ * @param string $Label
+ * @return true
+ * @throws Exception
+ */
+ public static function warn($Object, $Label = null)
+ {
+ return self::send($Object, $Label, FirePHP::WARN);
+ }
+
+ /**
+ * Log object with label to firebug console
+ *
+ * @see FirePHP::ERROR
+ * @param mixes $Object
+ * @param string $Label
+ * @return true
+ * @throws Exception
+ */
+ public static function error($Object, $Label = null)
+ {
+ return self::send($Object, $Label, FirePHP::ERROR);
+ }
+
+ /**
+ * Dumps key and variable to firebug server panel
+ *
+ * @see FirePHP::DUMP
+ * @param string $Key
+ * @param mixed $Variable
+ * @return true
+ * @throws Exception
+ */
+ public static function dump($Key, $Variable)
+ {
+ return self::send($Variable, $Key, FirePHP::DUMP);
+ }
+
+ /**
+ * Log a trace in the firebug console
+ *
+ * @see FirePHP::TRACE
+ * @param string $Label
+ * @return true
+ * @throws Exception
+ */
+ public static function trace($Label)
+ {
+ return self::send($Label, FirePHP::TRACE);
+ }
+
+ /**
+ * Log a table in the firebug console
+ *
+ * @see FirePHP::TABLE
+ * @param string $Label
+ * @param string $Table
+ * @return true
+ * @throws Exception
+ */
+ public static function table($Label, $Table)
+ {
+ return self::send($Table, $Label, FirePHP::TABLE);
+ }
+
+}
+
+if (!defined('E_STRICT')) {
+ define('E_STRICT', 2048);
+}
+if (!defined('E_RECOVERABLE_ERROR')) {
+ define('E_RECOVERABLE_ERROR', 4096);
+}
+if (!defined('E_DEPRECATED')) {
+ define('E_DEPRECATED', 8192);
+}
+if (!defined('E_USER_DEPRECATED')) {
+ define('E_USER_DEPRECATED', 16384);
+}
+
+/**
+ * Sends the given data to the FirePHP Firefox Extension.
+ * The data can be displayed in the Firebug Console or in the
+ * "Server" request tab.
+ *
+ * For more information see: http://www.firephp.org/
+ *
+ * @copyright Copyright (C) 2007-2009 Christoph Dorn
+ * @author Christoph Dorn
+ * @license http://www.opensource.org/licenses/bsd-license.php
+ * @package FirePHPCore
+ */
+class FirePHP
+{
+
+ /**
+ * FirePHP version
+ *
+ * @var string
+ */
+ const VERSION = '0.3'; // @pinf replace '0.3' with '%%package.version%%'
+
+ /**
+ * Firebug LOG level
+ *
+ * Logs a message to firebug console.
+ *
+ * @var string
+ */
+ const LOG = 'LOG';
+
+ /**
+ * Firebug INFO level
+ *
+ * Logs a message to firebug console and displays an info icon before the message.
+ *
+ * @var string
+ */
+ const INFO = 'INFO';
+
+ /**
+ * Firebug WARN level
+ *
+ * Logs a message to firebug console, displays an warning icon before the message and colors the line turquoise.
+ *
+ * @var string
+ */
+ const WARN = 'WARN';
+
+ /**
+ * Firebug ERROR level
+ *
+ * Logs a message to firebug console, displays an error icon before the message and colors the line yellow. Also increments the firebug error count.
+ *
+ * @var string
+ */
+ const ERROR = 'ERROR';
+
+ /**
+ * Dumps a variable to firebug's server panel
+ *
+ * @var string
+ */
+ const DUMP = 'DUMP';
+
+ /**
+ * Displays a stack trace in firebug console
+ *
+ * @var string
+ */
+ const TRACE = 'TRACE';
+
+ /**
+ * Displays an exception in firebug console
+ *
+ * Increments the firebug error count.
+ *
+ * @var string
+ */
+ const EXCEPTION = 'EXCEPTION';
+
+ /**
+ * Displays an table in firebug console
+ *
+ * @var string
+ */
+ const TABLE = 'TABLE';
+
+ /**
+ * Starts a group in firebug console
+ *
+ * @var string
+ */
+ const GROUP_START = 'GROUP_START';
+
+ /**
+ * Ends a group in firebug console
+ *
+ * @var string
+ */
+ const GROUP_END = 'GROUP_END';
+
+ /**
+ * Singleton instance of FirePHP
+ *
+ * @var FirePHP
+ */
+ protected static $instance = null;
+
+ /**
+ * Flag whether we are logging from within the exception handler
+ *
+ * @var boolean
+ */
+ protected $inExceptionHandler = false;
+
+ /**
+ * Flag whether to throw PHP errors that have been converted to ErrorExceptions
+ *
+ * @var boolean
+ */
+ protected $throwErrorExceptions = true;
+
+ /**
+ * Flag whether to convert PHP assertion errors to Exceptions
+ *
+ * @var boolean
+ */
+ protected $convertAssertionErrorsToExceptions = true;
+
+ /**
+ * Flag whether to throw PHP assertion errors that have been converted to Exceptions
+ *
+ * @var boolean
+ */
+ protected $throwAssertionExceptions = false;
+
+ /**
+ * Wildfire protocol message index
+ *
+ * @var int
+ */
+ protected $messageIndex = 1;
+
+ /**
+ * Options for the library
+ *
+ * @var array
+ */
+ protected $options = array('maxDepth' => 10,
+ 'maxObjectDepth' => 5,
+ 'maxArrayDepth' => 5,
+ 'useNativeJsonEncode' => true,
+ 'includeLineNumbers' => true);
+
+ /**
+ * Filters used to exclude object members when encoding
+ *
+ * @var array
+ */
+ protected $objectFilters = array(
+ 'firephp' => array('objectStack', 'instance', 'json_objectStack'),
+ 'firephp_test_class' => array('objectStack', 'instance', 'json_objectStack'),
+ );
+
+ /**
+ * A stack of objects used to detect recursion during object encoding
+ *
+ * @var object
+ */
+ protected $objectStack = array();
+
+ /**
+ * Flag to enable/disable logging
+ *
+ * @var boolean
+ */
+ protected $enabled = true;
+
+ /**
+ * The insight console to log to if applicable
+ *
+ * @var object
+ */
+ protected $logToInsightConsole = null;
+
+ /**
+ * When the object gets serialized only include specific object members.
+ *
+ * @return array
+ */
+ public function __sleep()
+ {
+ return array('options', 'objectFilters', 'enabled');
+ }
+
+ /**
+ * Gets singleton instance of FirePHP
+ *
+ * @param boolean $AutoCreate
+ * @return FirePHP
+ */
+ public static function getInstance($AutoCreate = false)
+ {
+ if (true === $AutoCreate && !self::$instance) {
+ self::init();
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Creates FirePHP object and stores it for singleton access
+ *
+ * @return FirePHP
+ */
+ public static function init()
+ {
+ return self::setInstance(new self());
+ }
+
+ /**
+ * Set the instance of the FirePHP singleton
+ *
+ * @param FirePHP $instance The FirePHP object instance
+ * @return FirePHP
+ */
+ public static function setInstance($instance)
+ {
+ return self::$instance = $instance;
+ }
+
+ /**
+ * Set an Insight console to direct all logging calls to
+ *
+ * @param object $console The console object to log to
+ * @return void
+ */
+ public function setLogToInsightConsole($console)
+ {
+ if (is_string($console)) {
+ if (get_class($this) != 'FirePHP_Insight' && !is_subclass_of($this, 'FirePHP_Insight')) {
+ throw new Exception('FirePHP instance not an instance or subclass of FirePHP_Insight!');
+ }
+ $this->logToInsightConsole = $this->to('request')->console($console);
+ } else {
+ $this->logToInsightConsole = $console;
+ }
+ }
+
+ /**
+ * Enable and disable logging to Firebug
+ *
+ * @param boolean $Enabled TRUE to enable, FALSE to disable
+ * @return void
+ */
+ public function setEnabled($Enabled)
+ {
+ $this->enabled = $Enabled;
+ }
+
+ /**
+ * Check if logging is enabled
+ *
+ * @return boolean TRUE if enabled
+ */
+ public function getEnabled()
+ {
+ return $this->enabled;
+ }
+
+ /**
+ * Specify a filter to be used when encoding an object
+ *
+ * Filters are used to exclude object members.
+ *
+ * @param string $Class The class name of the object
+ * @param array $Filter An array of members to exclude
+ * @return void
+ */
+ public function setObjectFilter($Class, $Filter)
+ {
+ $this->objectFilters[strtolower($Class)] = $Filter;
+ }
+
+ /**
+ * Set some options for the library
+ *
+ * Options:
+ * - maxDepth: The maximum depth to traverse (default: 10)
+ * - maxObjectDepth: The maximum depth to traverse objects (default: 5)
+ * - maxArrayDepth: The maximum depth to traverse arrays (default: 5)
+ * - useNativeJsonEncode: If true will use json_encode() (default: true)
+ * - includeLineNumbers: If true will include line numbers and filenames (default: true)
+ *
+ * @param array $Options The options to be set
+ * @return void
+ */
+ public function setOptions($Options)
+ {
+ $this->options = array_merge($this->options, $Options);
+ }
+
+ /**
+ * Get options from the library
+ *
+ * @return array The currently set options
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * Set an option for the library
+ *
+ * @param string $Name
+ * @param mixed $Value
+ * @throws Exception
+ * @return void
+ */
+ public function setOption($Name, $Value)
+ {
+ if (!isset($this->options[$Name])) {
+ throw $this->newException('Unknown option: ' . $Name);
+ }
+ $this->options[$Name] = $Value;
+ }
+
+ /**
+ * Get an option from the library
+ *
+ * @param string $Name
+ * @throws Exception
+ * @return mixed
+ */
+ public function getOption($Name)
+ {
+ if (!isset($this->options[$Name])) {
+ throw $this->newException('Unknown option: ' . $Name);
+ }
+ return $this->options[$Name];
+ }
+
+ /**
+ * Register FirePHP as your error handler
+ *
+ * Will throw exceptions for each php error.
+ *
+ * @return mixed Returns a string containing the previously defined error handler (if any)
+ */
+ public function registerErrorHandler($throwErrorExceptions = false)
+ {
+ //NOTE: The following errors will not be caught by this error handler:
+ // E_ERROR, E_PARSE, E_CORE_ERROR,
+ // E_CORE_WARNING, E_COMPILE_ERROR,
+ // E_COMPILE_WARNING, E_STRICT
+
+ $this->throwErrorExceptions = $throwErrorExceptions;
+
+ return set_error_handler(array($this, 'errorHandler'));
+ }
+
+ /**
+ * FirePHP's error handler
+ *
+ * Throws exception for each php error that will occur.
+ *
+ * @param int $errno
+ * @param string $errstr
+ * @param string $errfile
+ * @param int $errline
+ * @param array $errcontext
+ */
+ public function errorHandler($errno, $errstr, $errfile, $errline, $errcontext)
+ {
+ // Don't throw exception if error reporting is switched off
+ if (error_reporting() == 0) {
+ return;
+ }
+ // Only throw exceptions for errors we are asking for
+ if (error_reporting() & $errno) {
+
+ $exception = new ErrorException($errstr, 0, $errno, $errfile, $errline);
+ if ($this->throwErrorExceptions) {
+ throw $exception;
+ } else {
+ $this->fb($exception);
+ }
+ }
+ }
+
+ /**
+ * Register FirePHP as your exception handler
+ *
+ * @return mixed Returns the name of the previously defined exception handler,
+ * or NULL on error.
+ * If no previous handler was defined, NULL is also returned.
+ */
+ public function registerExceptionHandler()
+ {
+ return set_exception_handler(array($this, 'exceptionHandler'));
+ }
+
+ /**
+ * FirePHP's exception handler
+ *
+ * Logs all exceptions to your firebug console and then stops the script.
+ *
+ * @param Exception $Exception
+ * @throws Exception
+ */
+ public function exceptionHandler($Exception)
+ {
+
+ $this->inExceptionHandler = true;
+
+ header('HTTP/1.1 500 Internal Server Error');
+
+ try {
+ $this->fb($Exception);
+ } catch (Exception $e) {
+ echo 'We had an exception: ' . $e;
+ }
+ $this->inExceptionHandler = false;
+ }
+
+ /**
+ * Register FirePHP driver as your assert callback
+ *
+ * @param boolean $convertAssertionErrorsToExceptions
+ * @param boolean $throwAssertionExceptions
+ * @return mixed Returns the original setting or FALSE on errors
+ */
+ public function registerAssertionHandler($convertAssertionErrorsToExceptions = true, $throwAssertionExceptions = false)
+ {
+ $this->convertAssertionErrorsToExceptions = $convertAssertionErrorsToExceptions;
+ $this->throwAssertionExceptions = $throwAssertionExceptions;
+
+ if ($throwAssertionExceptions && !$convertAssertionErrorsToExceptions) {
+ throw $this->newException('Cannot throw assertion exceptions as assertion errors are not being converted to exceptions!');
+ }
+
+ return assert_options(ASSERT_CALLBACK, array($this, 'assertionHandler'));
+ }
+
+ /**
+ * FirePHP's assertion handler
+ *
+ * Logs all assertions to your firebug console and then stops the script.
+ *
+ * @param string $file File source of assertion
+ * @param int $line Line source of assertion
+ * @param mixed $code Assertion code
+ */
+ public function assertionHandler($file, $line, $code)
+ {
+ if ($this->convertAssertionErrorsToExceptions) {
+
+ $exception = new ErrorException('Assertion Failed - Code[ ' . $code . ' ]', 0, null, $file, $line);
+
+ if ($this->throwAssertionExceptions) {
+ throw $exception;
+ } else {
+ $this->fb($exception);
+ }
+
+ } else {
+ $this->fb($code, 'Assertion Failed', FirePHP::ERROR, array('File' => $file, 'Line' => $line));
+ }
+ }
+
+ /**
+ * Start a group for following messages.
+ *
+ * Options:
+ * Collapsed: [true|false]
+ * Color: [#RRGGBB|ColorName]
+ *
+ * @param string $Name
+ * @param array $Options OPTIONAL Instructions on how to log the group
+ * @return true
+ * @throws Exception
+ */
+ public function group($Name, $Options = null)
+ {
+
+ if (!$Name) {
+ throw $this->newException('You must specify a label for the group!');
+ }
+
+ if ($Options) {
+ if (!is_array($Options)) {
+ throw $this->newException('Options must be defined as an array!');
+ }
+ if (array_key_exists('Collapsed', $Options)) {
+ $Options['Collapsed'] = ($Options['Collapsed']) ? 'true' : 'false';
+ }
+ }
+
+ return $this->fb(null, $Name, FirePHP::GROUP_START, $Options);
+ }
+
+ /**
+ * Ends a group you have started before
+ *
+ * @return true
+ * @throws Exception
+ */
+ public function groupEnd()
+ {
+ return $this->fb(null, null, FirePHP::GROUP_END);
+ }
+
+ /**
+ * Log object with label to firebug console
+ *
+ * @see FirePHP::LOG
+ * @param mixes $Object
+ * @param string $Label
+ * @return true
+ * @throws Exception
+ */
+ public function log($Object, $Label = null, $Options = array())
+ {
+ return $this->fb($Object, $Label, FirePHP::LOG, $Options);
+ }
+
+ /**
+ * Log object with label to firebug console
+ *
+ * @see FirePHP::INFO
+ * @param mixes $Object
+ * @param string $Label
+ * @return true
+ * @throws Exception
+ */
+ public function info($Object, $Label = null, $Options = array())
+ {
+ return $this->fb($Object, $Label, FirePHP::INFO, $Options);
+ }
+
+ /**
+ * Log object with label to firebug console
+ *
+ * @see FirePHP::WARN
+ * @param mixes $Object
+ * @param string $Label
+ * @return true
+ * @throws Exception
+ */
+ public function warn($Object, $Label = null, $Options = array())
+ {
+ return $this->fb($Object, $Label, FirePHP::WARN, $Options);
+ }
+
+ /**
+ * Log object with label to firebug console
+ *
+ * @see FirePHP::ERROR
+ * @param mixes $Object
+ * @param string $Label
+ * @return true
+ * @throws Exception
+ */
+ public function error($Object, $Label = null, $Options = array())
+ {
+ return $this->fb($Object, $Label, FirePHP::ERROR, $Options);
+ }
+
+ /**
+ * Dumps key and variable to firebug server panel
+ *
+ * @see FirePHP::DUMP
+ * @param string $Key
+ * @param mixed $Variable
+ * @return true
+ * @throws Exception
+ */
+ public function dump($Key, $Variable, $Options = array())
+ {
+ if (!is_string($Key)) {
+ throw $this->newException('Key passed to dump() is not a string');
+ }
+ if (strlen($Key) > 100) {
+ throw $this->newException('Key passed to dump() is longer than 100 characters');
+ }
+ if (!preg_match_all('/^[a-zA-Z0-9-_\.:]*$/', $Key, $m)) {
+ throw $this->newException('Key passed to dump() contains invalid characters [a-zA-Z0-9-_\.:]');
+ }
+ return $this->fb($Variable, $Key, FirePHP::DUMP, $Options);
+ }
+
+ /**
+ * Log a trace in the firebug console
+ *
+ * @see FirePHP::TRACE
+ * @param string $Label
+ * @return true
+ * @throws Exception
+ */
+ public function trace($Label)
+ {
+ return $this->fb($Label, FirePHP::TRACE);
+ }
+
+ /**
+ * Log a table in the firebug console
+ *
+ * @see FirePHP::TABLE
+ * @param string $Label
+ * @param string $Table
+ * @return true
+ * @throws Exception
+ */
+ public function table($Label, $Table, $Options = array())
+ {
+ return $this->fb($Table, $Label, FirePHP::TABLE, $Options);
+ }
+
+ /**
+ * Insight API wrapper
+ *
+ * @see Insight_Helper::to()
+ */
+ public static function to()
+ {
+ $instance = self::getInstance();
+ if (!method_exists($instance, "_to")) {
+ throw new Exception("FirePHP::to() implementation not loaded");
+ }
+ $args = func_get_args();
+ return call_user_func_array(array($instance, '_to'), $args);
+ }
+
+ /**
+ * Insight API wrapper
+ *
+ * @see Insight_Helper::plugin()
+ */
+ public static function plugin()
+ {
+ $instance = self::getInstance();
+ if (!method_exists($instance, "_plugin")) {
+ throw new Exception("FirePHP::plugin() implementation not loaded");
+ }
+ $args = func_get_args();
+ return call_user_func_array(array($instance, '_plugin'), $args);
+ }
+
+ /**
+ * Check if FirePHP is installed on client
+ *
+ * @return boolean
+ */
+ public function detectClientExtension()
+ {
+ // Check if FirePHP is installed on client via User-Agent header
+ if (@preg_match_all('/\sFirePHP\/([\.\d]*)\s?/si', $this->getUserAgent(), $m) &&
+ version_compare($m[1][0], '0.0.6', '>=')) {
+ return true;
+ } else
+ // Check if FirePHP is installed on client via X-FirePHP-Version header
+ if (@preg_match_all('/^([\.\d]*)$/si', $this->getRequestHeader("X-FirePHP-Version"), $m) &&
+ version_compare($m[1][0], '0.0.6', '>=')) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Log varible to Firebug
+ *
+ * @see http://www.firephp.org/Wiki/Reference/Fb
+ * @param mixed $Object The variable to be logged
+ * @return true Return TRUE if message was added to headers, FALSE otherwise
+ * @throws Exception
+ */
+ public function fb($Object)
+ {
+ if ($this instanceof FirePHP_Insight && method_exists($this, '_logUpgradeClientMessage')) {
+ if (!FirePHP_Insight::$upgradeClientMessageLogged) {
+ // avoid infinite recursion as _logUpgradeClientMessage() logs a message
+ $this->_logUpgradeClientMessage();
+ }
+ }
+
+ static $insightGroupStack = array();
+
+ if (!$this->getEnabled()) {
+ return false;
+ }
+
+ if ($this->headersSent($filename, $linenum)) {
+ // If we are logging from within the exception handler we cannot throw another exception
+ if ($this->inExceptionHandler) {
+ // Simply echo the error out to the page
+ echo 'FirePHP ERROR: Headers already sent in ' . $filename . ' on line ' . $linenum . ' . Cannot send log data to FirePHP. You must have Output Buffering enabled via ob_start() or output_buffering ini directive.
';
+ } else {
+ throw $this->newException('Headers already sent in ' . $filename . ' on line ' . $linenum . '. Cannot send log data to FirePHP. You must have Output Buffering enabled via ob_start() or output_buffering ini directive.');
+ }
+ }
+
+ $Type = null;
+ $Label = null;
+ $Options = array();
+
+ if (func_num_args() == 1) {
+ } else
+ if (func_num_args() == 2) {
+ switch (func_get_arg(1)) {
+ case self::LOG:
+ case self::INFO:
+ case self::WARN:
+ case self::ERROR:
+ case self::DUMP:
+ case self::TRACE:
+ case self::EXCEPTION:
+ case self::TABLE:
+ case self::GROUP_START:
+ case self::GROUP_END:
+ $Type = func_get_arg(1);
+ break;
+ default:
+ $Label = func_get_arg(1);
+ break;
+ }
+ } else
+ if (func_num_args() == 3) {
+ $Type = func_get_arg(2);
+ $Label = func_get_arg(1);
+ } else
+ if (func_num_args() == 4) {
+ $Type = func_get_arg(2);
+ $Label = func_get_arg(1);
+ $Options = func_get_arg(3);
+ } else {
+ throw $this->newException('Wrong number of arguments to fb() function!');
+ }
+
+ if (null !== $this->logToInsightConsole && (get_class($this) == 'FirePHP_Insight' || is_subclass_of($this, 'FirePHP_Insight'))) {
+ $msg = $this->logToInsightConsole;
+ if ($Object instanceof Exception) {
+ $Type = self::EXCEPTION;
+ }
+ if ($Label && self::TABLE != $Type && self::GROUP_START != $Type) {
+ $msg = $msg->label($Label);
+ }
+ switch ($Type) {
+ case self::DUMP:
+ case self::LOG:
+ return $msg->log($Object);
+ case self::INFO:
+ return $msg->info($Object);
+ case self::WARN:
+ return $msg->warn($Object);
+ case self::ERROR:
+ return $msg->error($Object);
+ case self::TRACE:
+ return $msg->trace($Object);
+ case self::EXCEPTION:
+ return $this->plugin('engine')->handleException($Object, $msg);
+ case self::TABLE:
+ if (isset($Object[0]) && !is_string($Object[0]) && $Label) {
+ $Object = array($Label, $Object);
+ }
+ return $msg->table($Object[0], array_slice($Object[1], 1), $Object[1][0]);
+ case self::GROUP_START:
+ $insightGroupStack[] = $msg->group(md5($Label))->open();
+ return $msg->log($Label);
+ case self::GROUP_END:
+ if (count($insightGroupStack) == 0) {
+ throw new Error('Too many groupEnd() as opposed to group() calls!');
+ }
+ $group = array_pop($insightGroupStack);
+ return $group->close();
+ default:
+ return $msg->log($Object);
+ }
+ }
+
+ if (!$this->detectClientExtension()) {
+ return false;
+ }
+
+ $meta = array();
+ $skipFinalObjectEncode = false;
+
+ if ($Object instanceof Exception) {
+
+ $meta['file'] = $this->_escapeTraceFile($Object->getFile());
+ $meta['line'] = $Object->getLine();
+
+ $trace = $Object->getTrace();
+ if ($Object instanceof ErrorException
+ && isset($trace[0]['function'])
+ && 'errorHandler' == $trace[0]['function'] && isset($trace[0]['class'])
+ && 'FirePHP' == $trace[0]['class']) {
+
+ $severity = false;
+ switch ($Object->getSeverity()) {
+ case E_WARNING:$severity = 'E_WARNING';
+ break;
+ case E_NOTICE:$severity = 'E_NOTICE';
+ break;
+ case E_USER_ERROR:$severity = 'E_USER_ERROR';
+ break;
+ case E_USER_WARNING:$severity = 'E_USER_WARNING';
+ break;
+ case E_USER_NOTICE:$severity = 'E_USER_NOTICE';
+ break;
+ case E_STRICT:$severity = 'E_STRICT';
+ break;
+ case E_RECOVERABLE_ERROR:$severity = 'E_RECOVERABLE_ERROR';
+ break;
+ case E_DEPRECATED:$severity = 'E_DEPRECATED';
+ break;
+ case E_USER_DEPRECATED:$severity = 'E_USER_DEPRECATED';
+ break;
+ }
+
+ $Object = array('Class' => get_class($Object),
+ 'Message' => $severity . ': ' . $Object->getMessage(),
+ 'File' => $this->_escapeTraceFile($Object->getFile()),
+ 'Line' => $Object->getLine(),
+ 'Type' => 'trigger',
+ 'Trace' => $this->_escapeTrace(array_splice($trace, 2)));
+ $skipFinalObjectEncode = true;
+ } else {
+ $Object = array('Class' => get_class($Object),
+ 'Message' => $Object->getMessage(),
+ 'File' => $this->_escapeTraceFile($Object->getFile()),
+ 'Line' => $Object->getLine(),
+ 'Type' => 'throw',
+ 'Trace' => $this->_escapeTrace($trace));
+ $skipFinalObjectEncode = true;
+ }
+ $Type = self::EXCEPTION;
+
+ } else
+ if (self::TRACE == $Type) {
+
+ $trace = debug_backtrace();
+ if (!$trace) {
+ return false;
+ }
+
+ for ($i = 0; $i < sizeof($trace); $i++) {
+
+ if (isset($trace[$i]['class'])
+ && isset($trace[$i]['file'])
+ && ('FirePHP' == $trace[$i]['class'] || 'FB' == $trace[$i]['class'])
+ && (substr($this->_standardizePath($trace[$i]['file']), -18, 18) == 'FirePHPCore/fb.php'
+ || substr($this->_standardizePath($trace[$i]['file']), -29, 29) == 'FirePHPCore/FirePHP.class.php')) {
+ /* Skip - FB::trace(), FB::send(), $firephp->trace(), $firephp->fb() */
+ } else
+ if (isset($trace[$i]['class'])
+ && isset($trace[$i + 1]['file'])
+ && 'FirePHP' == $trace[$i]['class'] && substr($this->_standardizePath($trace[$i + 1]['file']), -18, 18) == 'FirePHPCore/fb.php') {
+ /* Skip fb() */
+ } else
+ if ('fb' == $trace[$i]['function'] || 'trace' == $trace[$i]['function'] || 'send' == $trace[$i]['function']) {
+
+ $Object = array('Class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : '',
+ 'Type' => isset($trace[$i]['type']) ? $trace[$i]['type'] : '',
+ 'Function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : '',
+ 'Message' => $trace[$i]['args'][0],
+ 'File' => isset($trace[$i]['file']) ? $this->_escapeTraceFile($trace[$i]['file']) : '',
+ 'Line' => isset($trace[$i]['line']) ? $trace[$i]['line'] : '',
+ 'Args' => isset($trace[$i]['args']) ? $this->encodeObject($trace[$i]['args']) : '',
+ 'Trace' => $this->_escapeTrace(array_splice($trace, $i + 1)));
+
+ $skipFinalObjectEncode = true;
+ $meta['file'] = isset($trace[$i]['file']) ? $this->_escapeTraceFile($trace[$i]['file']) : '';
+ $meta['line'] = isset($trace[$i]['line']) ? $trace[$i]['line'] : '';
+ break;
+ }
+ }
+
+ } else
+ if (self::TABLE == $Type) {
+
+ if (isset($Object[0]) && is_string($Object[0])) {
+ $Object[1] = $this->encodeTable($Object[1]);
+ } else {
+ $Object = $this->encodeTable($Object);
+ }
+
+ $skipFinalObjectEncode = true;
+
+ } else
+ if (self::GROUP_START == $Type) {
+
+ if (!$Label) {
+ throw $this->newException('You must specify a label for the group!');
+ }
+
+ } else {
+ if (null === $Type) {
+ $Type = self::LOG;
+ }
+ }
+
+ if ($this->options['includeLineNumbers']) {
+ if (!isset($meta['file']) || !isset($meta['line'])) {
+
+ $trace = debug_backtrace();
+ for ($i = 0; $trace && $i < sizeof($trace); $i++) {
+
+ if (isset($trace[$i]['class'])
+ && isset($trace[$i]['file'])
+ && ('FirePHP' == $trace[$i]['class'] || 'FB' == $trace[$i]['class'])
+ && (substr($this->_standardizePath($trace[$i]['file']), -18, 18) == 'FirePHPCore/fb.php'
+ || substr($this->_standardizePath($trace[$i]['file']), -29, 29) == 'FirePHPCore/FirePHP.class.php')) {
+ /* Skip - FB::trace(), FB::send(), $firephp->trace(), $firephp->fb() */
+ } else
+ if (isset($trace[$i]['class'])
+ && isset($trace[$i + 1]['file'])
+ && 'FirePHP' == $trace[$i]['class'] && substr($this->_standardizePath($trace[$i + 1]['file']), -18, 18) == 'FirePHPCore/fb.php') {
+ /* Skip fb() */
+ } else
+ if (isset($trace[$i]['file'])
+ && substr($this->_standardizePath($trace[$i]['file']), -18, 18) == 'FirePHPCore/fb.php') {
+ /* Skip FB::fb() */
+ } else {
+ $meta['file'] = isset($trace[$i]['file']) ? $this->_escapeTraceFile($trace[$i]['file']) : '';
+ $meta['line'] = isset($trace[$i]['line']) ? $trace[$i]['line'] : '';
+ break;
+ }
+ }
+ }
+ } else {
+ unset($meta['file']);
+ unset($meta['line']);
+ }
+
+ $this->setHeader('X-Wf-Protocol-1', 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2');
+ $this->setHeader('X-Wf-1-Plugin-1', 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/' . self::VERSION);
+
+ $structure_index = 1;
+ if (self::DUMP == $Type) {
+ $structure_index = 2;
+ $this->setHeader('X-Wf-1-Structure-2', 'http://meta.firephp.org/Wildfire/Structure/FirePHP/Dump/0.1');
+ } else {
+ $this->setHeader('X-Wf-1-Structure-1', 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1');
+ }
+
+ if (self::DUMP == $Type) {
+ $msg = '{"' . $Label . '":' . $this->jsonEncode($Object, $skipFinalObjectEncode) . '}';
+ } else {
+ $msg_meta = $Options;
+ $msg_meta['Type'] = $Type;
+ if (null !== $Label) {
+ $msg_meta['Label'] = $Label;
+ }
+ if (isset($meta['file']) && !isset($msg_meta['File'])) {
+ $msg_meta['File'] = $meta['file'];
+ }
+ if (isset($meta['line']) && !isset($msg_meta['Line'])) {
+ $msg_meta['Line'] = $meta['line'];
+ }
+ $msg = '[' . $this->jsonEncode($msg_meta) . ',' . $this->jsonEncode($Object, $skipFinalObjectEncode) . ']';
+ }
+
+ $parts = explode("\n", chunk_split($msg, 5000, "\n"));
+
+ for ($i = 0; $i < count($parts); $i++) {
+
+ $part = $parts[$i];
+ if ($part) {
+
+ if (count($parts) > 2) {
+ // Message needs to be split into multiple parts
+ $this->setHeader('X-Wf-1-' . $structure_index . '-' . '1-' . $this->messageIndex,
+ ((0 == $i) ? strlen($msg) : '')
+ . '|' . $part . '|'
+ . (($i < count($parts) - 2) ? '\\' : ''));
+ } else {
+ $this->setHeader('X-Wf-1-' . $structure_index . '-' . '1-' . $this->messageIndex,
+ strlen($part) . '|' . $part . '|');
+ }
+
+ $this->messageIndex++;
+
+ if ($this->messageIndex > 99999) {
+ throw $this->newException('Maximum number (99,999) of messages reached!');
+ }
+ }
+ }
+
+ $this->setHeader('X-Wf-1-Index', $this->messageIndex - 1);
+
+ return true;
+ }
+
+ /**
+ * Standardizes path for windows systems.
+ *
+ * @param string $Path
+ * @return string
+ */
+ protected function _standardizePath($Path)
+ {
+ return preg_replace('/\\\\+/', '/', $Path);
+ }
+
+ /**
+ * Escape trace path for windows systems
+ *
+ * @param array $Trace
+ * @return array
+ */
+ protected function _escapeTrace($Trace)
+ {
+ if (!$Trace) {
+ return $Trace;
+ }
+
+ for ($i = 0; $i < sizeof($Trace); $i++) {
+ if (isset($Trace[$i]['file'])) {
+ $Trace[$i]['file'] = $this->_escapeTraceFile($Trace[$i]['file']);
+ }
+ if (isset($Trace[$i]['args'])) {
+ $Trace[$i]['args'] = $this->encodeObject($Trace[$i]['args']);
+ }
+ }
+ return $Trace;
+ }
+
+ /**
+ * Escape file information of trace for windows systems
+ *
+ * @param string $File
+ * @return string
+ */
+ protected function _escapeTraceFile($File)
+ {
+ /* Check if we have a windows filepath */
+ if (strpos($File, '\\')) {
+ /* First strip down to single \ */
+
+ $file = preg_replace('/\\\\+/', '\\', $File);
+
+ return $file;
+ }
+ return $File;
+ }
+
+ /**
+ * Check if headers have already been sent
+ *
+ * @param string $Filename
+ * @param integer $Linenum
+ */
+ protected function headersSent(&$Filename, &$Linenum)
+ {
+ return headers_sent($Filename, $Linenum);
+ }
+
+ /**
+ * Send header
+ *
+ * @param string $Name
+ * @param string $Value
+ */
+ protected function setHeader($Name, $Value)
+ {
+ return header($Name . ': ' . $Value);
+ }
+
+ /**
+ * Get user agent
+ *
+ * @return string|false
+ */
+ protected function getUserAgent()
+ {
+ if (!isset($_SERVER['HTTP_USER_AGENT'])) {
+ return false;
+ }
+
+ return $_SERVER['HTTP_USER_AGENT'];
+ }
+
+ /**
+ * Get all request headers
+ *
+ * @return array
+ */
+ public static function getAllRequestHeaders()
+ {
+ static $_cached_headers = false;
+ if (false !== $_cached_headers) {
+ return $_cached_headers;
+ }
+ $headers = array();
+ if (function_exists('getallheaders')) {
+ foreach (getallheaders() as $name => $value) {
+ $headers[strtolower($name)] = $value;
+ }
+ } else {
+ foreach ($_SERVER as $name => $value) {
+ if (substr($name, 0, 5) == 'HTTP_') {
+ $headers[strtolower(str_replace(' ', '-', str_replace('_', ' ', substr($name, 5))))] = $value;
+ }
+ }
+ }
+ return $_cached_headers = $headers;
+ }
+
+ /**
+ * Get a request header
+ *
+ * @return string|false
+ */
+ protected function getRequestHeader($Name)
+ {
+ $headers = self::getAllRequestHeaders();
+ if (isset($headers[strtolower($Name)])) {
+ return $headers[strtolower($Name)];
+ }
+ return false;
+ }
+
+ /**
+ * Returns a new exception
+ *
+ * @param string $Message
+ * @return Exception
+ */
+ protected function newException($Message)
+ {
+ return new Exception($Message);
+ }
+
+ /**
+ * Encode an object into a JSON string
+ *
+ * Uses PHP's jeson_encode() if available
+ *
+ * @param object $Object The object to be encoded
+ * @return string The JSON string
+ */
+ public function jsonEncode($Object, $skipObjectEncode = false)
+ {
+ if (!$skipObjectEncode) {
+ $Object = $this->encodeObject($Object);
+ }
+
+ if (function_exists('json_encode')
+ && false != $this->options['useNativeJsonEncode']) {
+
+ return jsonEncode($Object);
+ } else {
+ return $this->jsonEncode($Object);
+ }
+ }
+
+ /**
+ * Encodes a table by encoding each row and column with encodeObject()
+ *
+ * @param array $Table The table to be encoded
+ * @return array
+ */
+ protected function encodeTable($Table)
+ {
+
+ if (!$Table) {
+ return $Table;
+ }
+
+ $new_table = array();
+ foreach ($Table as $row) {
+
+ if (is_array($row)) {
+ $new_row = array();
+
+ foreach ($row as $item) {
+ $new_row[] = $this->encodeObject($item);
+ }
+
+ $new_table[] = $new_row;
+ }
+ }
+
+ return $new_table;
+ }
+
+ /**
+ * Encodes an object including members with
+ * protected and private visibility
+ *
+ * @param Object $Object The object to be encoded
+ * @param int $Depth The current traversal depth
+ * @return array All members of the object
+ */
+ protected function encodeObject($Object, $ObjectDepth = 1, $ArrayDepth = 1, $MaxDepth = 1)
+ {
+ if ($MaxDepth > $this->options['maxDepth']) {
+ return '** Max Depth (' . $this->options['maxDepth'] . ') **';
+ }
+
+ $return = array();
+
+ if (is_resource($Object)) {
+
+ return '** ' . (string) $Object . ' **';
+
+ } else
+ if (is_object($Object)) {
+
+ if ($ObjectDepth > $this->options['maxObjectDepth']) {
+ return '** Max Object Depth (' . $this->options['maxObjectDepth'] . ') **';
+ }
+
+ foreach ($this->objectStack as $refVal) {
+ if ($refVal === $Object) {
+ return '** Recursion (' . get_class($Object) . ') **';
+ }
+ }
+ array_push($this->objectStack, $Object);
+
+ $return['__className'] = $class = get_class($Object);
+ $class_lower = strtolower($class);
+
+ $reflectionClass = new ReflectionClass($class);
+ $properties = array();
+ foreach ($reflectionClass->getProperties() as $property) {
+ $properties[$property->getName()] = $property;
+ }
+
+ $members = (array) $Object;
+
+ foreach ($properties as $plain_name => $property) {
+
+ $name = $raw_name = $plain_name;
+ if ($property->isStatic()) {
+ $name = 'static:' . $name;
+ }
+ if ($property->isPublic()) {
+ $name = 'public:' . $name;
+ } else
+ if ($property->isPrivate()) {
+ $name = 'private:' . $name;
+ $raw_name = "\0" . $class . "\0" . $raw_name;
+ } else
+ if ($property->isProtected()) {
+ $name = 'protected:' . $name;
+ $raw_name = "\0" . '*' . "\0" . $raw_name;
+ }
+
+ if (!(isset($this->objectFilters[$class_lower])
+ && is_array($this->objectFilters[$class_lower])
+ && in_array($plain_name, $this->objectFilters[$class_lower]))) {
+
+ if (array_key_exists($raw_name, $members)
+ && !$property->isStatic()) {
+
+ $return[$name] = $this->encodeObject($members[$raw_name], $ObjectDepth + 1, 1, $MaxDepth + 1);
+
+ } else {
+ if (method_exists($property, 'setAccessible')) {
+ $property->setAccessible(true);
+ $return[$name] = $this->encodeObject($property->getValue($Object), $ObjectDepth + 1, 1, $MaxDepth + 1);
+ } else
+ if ($property->isPublic()) {
+ $return[$name] = $this->encodeObject($property->getValue($Object), $ObjectDepth + 1, 1, $MaxDepth + 1);
+ } else {
+ $return[$name] = '** Need PHP 5.3 to get value **';
+ }
+ }
+ } else {
+ $return[$name] = '** Excluded by Filter **';
+ }
+ }
+
+ // Include all members that are not defined in the class
+ // but exist in the object
+ foreach ($members as $raw_name => $value) {
+
+ $name = $raw_name;
+
+ if ("\0" == $name{0}) {
+ $parts = explode("\0", $name);
+ $name = $parts[2];
+ }
+
+ $plain_name = $name;
+
+ if (!isset($properties[$name])) {
+ $name = 'undeclared:' . $name;
+
+ if (!(isset($this->objectFilters[$class_lower])
+ && is_array($this->objectFilters[$class_lower])
+ && in_array($plain_name, $this->objectFilters[$class_lower]))) {
+
+ $return[$name] = $this->encodeObject($value, $ObjectDepth + 1, 1, $MaxDepth + 1);
+ } else {
+ $return[$name] = '** Excluded by Filter **';
+ }
+ }
+ }
+
+ array_pop($this->objectStack);
+
+ } elseif (is_array($Object)) {
+
+ if ($ArrayDepth > $this->options['maxArrayDepth']) {
+ return '** Max Array Depth (' . $this->options['maxArrayDepth'] . ') **';
+ }
+
+ foreach ($Object as $key => $val) {
+
+ // Encoding the $GLOBALS PHP array causes an infinite loop
+ // if the recursion is not reset here as it contains
+ // a reference to itself. This is the only way I have come up
+ // with to stop infinite recursion in this case.
+ if ('GLOBALS' == $key && is_array($val)
+ && array_key_exists('GLOBALS', $val)) {
+ $val['GLOBALS'] = '** Recursion (GLOBALS) **';
+ }
+
+ $return[$key] = $this->encodeObject($val, 1, $ArrayDepth + 1, $MaxDepth + 1);
+ }
+ } else {
+ if (self::isUtf8($Object)) {
+ return $Object;
+ } else {
+ return utf8_encode($Object);
+ }
+ }
+ return $return;
+ }
+
+ /**
+ * Returns true if $string is valid UTF-8 and false otherwise.
+ *
+ * @param mixed $str String to be tested
+ * @return boolean
+ */
+ protected static function isUtf8($str)
+ {
+ if (function_exists('mb_detect_encoding')) {
+ return (mb_detect_encoding($str) == 'UTF-8');
+ }
+ $c = 0;
+ $b = 0;
+ $bits = 0;
+ $len = strlen($str);
+ for ($i = 0; $i < $len; $i++) {
+ $c = ord($str[$i]);
+ if ($c > 128) {
+ if (($c >= 254)) {
+ return false;
+ } elseif ($c >= 252) {
+ $bits = 6;
+ } elseif ($c >= 248) {
+ $bits = 5;
+ } elseif ($c >= 240) {
+ $bits = 4;
+ } elseif ($c >= 224) {
+ $bits = 3;
+ } elseif ($c >= 192) {
+ $bits = 2;
+ } else {
+ return false;
+ }
+
+ if (($i + $bits) > $len) {
+ return false;
+ }
+
+ while ($bits > 1) {
+ $i++;
+ $b = ord($str[$i]);
+ if ($b < 128 || $b > 191) {
+ return false;
+ }
+
+ $bits--;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Converts to and from JSON format.
+ *
+ * JSON (JavaScript Object Notation) is a lightweight data-interchange
+ * format. It is easy for humans to read and write. It is easy for machines
+ * to parse and generate. It is based on a subset of the JavaScript
+ * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
+ * This feature can also be found in Python. JSON is a text format that is
+ * completely language independent but uses conventions that are familiar
+ * to programmers of the C-family of languages, including C, C++, C#, Java,
+ * JavaScript, Perl, TCL, and many others. These properties make JSON an
+ * ideal data-interchange language.
+ *
+ * This package provides a simple encoder and decoder for JSON notation. It
+ * is intended for use with client-side Javascript applications that make
+ * use of HTTPRequest to perform server communication functions - data can
+ * be encoded into JSON notation for use in a client-side javascript, or
+ * decoded from incoming Javascript requests. JSON format is native to
+ * Javascript, and can be directly eval()'ed with no further parsing
+ * overhead
+ *
+ * All strings should be in ASCII or UTF-8 format!
+ *
+ * LICENSE: Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met: Redistributions of source code must retain the
+ * above copyright notice, this list of conditions and the following
+ * disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * @category
+ * @package Services_JSON
+ * @author Michal Migurski
+ * @author Matt Knapp
+ * @author Brett Stimmerman
+ * @author Christoph Dorn
+ * @copyright 2005 Michal Migurski
+ * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
+ * @license http://www.opensource.org/licenses/bsd-license.php
+ * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
+ */
+
+ /**
+ * Keep a list of objects as we descend into the array so we can detect recursion.
+ */
+ private $json_objectStack = array();
+
+ /**
+ * convert a string from one UTF-8 char to one UTF-16 char
+ *
+ * Normally should be handled by mb_convert_encoding, but
+ * provides a slower PHP-only method for installations
+ * that lack the multibye string extension.
+ *
+ * @param string $utf8 UTF-8 character
+ * @return string UTF-16 character
+ * @access private
+ */
+ private function jsonUtf82utf16($utf8)
+ {
+ // oh please oh please oh please oh please oh please
+ if (function_exists('mb_convert_encoding')) {
+ return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
+ }
+
+ switch (strlen($utf8)) {
+ case 1:
+ // this case should never be reached, because we are in ASCII range
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return $utf8;
+
+ case 2:
+ // return a UTF-16 character from a 2-byte UTF-8 char
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0x07 & (ord($utf8{0}) >> 2))
+ . chr((0xC0 & (ord($utf8{0}) << 6))
+ | (0x3F & ord($utf8{1})));
+
+ case 3:
+ // return a UTF-16 character from a 3-byte UTF-8 char
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr((0xF0 & (ord($utf8{0}) << 4))
+ | (0x0F & (ord($utf8{1}) >> 2)))
+ . chr((0xC0 & (ord($utf8{1}) << 6))
+ | (0x7F & ord($utf8{2})));
+ }
+
+ // ignoring UTF-32 for now, sorry
+ return '';
+ }
+
+ /**
+ * encodes an arbitrary variable into JSON format
+ *
+ * @param mixed $var any number, boolean, string, array, or object to be encoded.
+ * see argument 1 to Services_JSON() above for array-parsing behavior.
+ * if var is a strng, note that encode() always expects it
+ * to be in ASCII or UTF-8 format!
+ *
+ * @return mixed JSON string representation of input var or an error if a problem occurs
+ * @access public
+ */
+ private function jsonEncode($var)
+ {
+
+ if (is_object($var)) {
+ if (in_array($var, $this->json_objectStack)) {
+ return '"** Recursion **"';
+ }
+ }
+
+ switch (gettype($var)) {
+ case 'boolean':
+ return $var ? 'true' : 'false';
+
+ case 'NULL':
+ return 'null';
+
+ case 'integer':
+ return (int) $var;
+
+ case 'double':
+ case 'float':
+ return (float) $var;
+
+ case 'string':
+ // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
+ $ascii = '';
+ $strlen_var = strlen($var);
+
+ /*
+ * Iterate over every character in the string,
+ * escaping with a slash or encoding to UTF-8 where necessary
+ */
+ for ($c = 0; $c < $strlen_var; ++$c) {
+
+ $ord_var_c = ord($var{$c});
+
+ switch (true) {
+ case 0x08 == $ord_var_c:
+ $ascii .= '\b';
+ break;
+ case 0x09 == $ord_var_c:
+ $ascii .= '\t';
+ break;
+ case 0x0A == $ord_var_c:
+ $ascii .= '\n';
+ break;
+ case 0x0C == $ord_var_c:
+ $ascii .= '\f';
+ break;
+ case 0x0D == $ord_var_c:
+ $ascii .= '\r';
+ break;
+
+ case 0x22 == $ord_var_c:
+ case 0x2F == $ord_var_c:
+ case 0x5C == $ord_var_c:
+ // double quote, slash, slosh
+ $ascii .= '\\' . $var{$c};
+ break;
+
+ case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
+ // characters U-00000000 - U-0000007F (same as ASCII)
+ $ascii .= $var{$c};
+ break;
+
+ case (($ord_var_c & 0xE0) == 0xC0):
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
+ $c += 1;
+ $utf16 = $this->jsonUtf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xF0) == 0xE0):
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}));
+ $c += 2;
+ $utf16 = $this->jsonUtf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xF8) == 0xF0):
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}));
+ $c += 3;
+ $utf16 = $this->jsonUtf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xFC) == 0xF8):
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}),
+ ord($var{$c + 4}));
+ $c += 4;
+ $utf16 = $this->jsonUtf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xFE) == 0xFC):
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}),
+ ord($var{$c + 4}),
+ ord($var{$c + 5}));
+ $c += 5;
+ $utf16 = $this->jsonUtf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+ }
+ }
+
+ return '"' . $ascii . '"';
+
+ case 'array':
+ /*
+ * As per JSON spec if any array key is not an integer
+ * we must treat the the whole array as an object. We
+ * also try to catch a sparsely populated associative
+ * array with numeric keys here because some JS engines
+ * will create an array with empty indexes up to
+ * max_index which can cause memory issues and because
+ * the keys, which may be relevant, will be remapped
+ * otherwise.
+ *
+ * As per the ECMA and JSON specification an object may
+ * have any string as a property. Unfortunately due to
+ * a hole in the ECMA specification if the key is a
+ * ECMA reserved word or starts with a digit the
+ * parameter is only accessible using ECMAScript's
+ * bracket notation.
+ */
+
+ // treat as a JSON object
+ if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
+
+ $this->json_objectStack[] = $var;
+
+ $properties = array_map(array($this, 'json_name_value'),
+ array_keys($var),
+ array_values($var));
+
+ array_pop($this->json_objectStack);
+
+ foreach ($properties as $property) {
+ if ($property instanceof Exception) {
+ return $property;
+ }
+ }
+
+ return '{' . join(',', $properties) . '}';
+ }
+
+ $this->json_objectStack[] = $var;
+
+ // treat it like a regular array
+ $elements = array_map(array($this, 'json_encode'), $var);
+
+ array_pop($this->json_objectStack);
+
+ foreach ($elements as $element) {
+ if ($element instanceof Exception) {
+ return $element;
+ }
+ }
+
+ return '[' . join(',', $elements) . ']';
+
+ case 'object':
+ $vars = self::encodeObject($var);
+
+ $this->json_objectStack[] = $var;
+
+ $properties = array_map(array($this, 'json_name_value'),
+ array_keys($vars),
+ array_values($vars));
+
+ array_pop($this->json_objectStack);
+
+ foreach ($properties as $property) {
+ if ($property instanceof Exception) {
+ return $property;
+ }
+ }
+
+ return '{' . join(',', $properties) . '}';
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * array-walking function for use in generating JSON-formatted name-value pairs
+ *
+ * @param string $name name of key to use
+ * @param mixed $value reference to an array element to be encoded
+ *
+ * @return string JSON-formatted name-value pair, like '"name":value'
+ * @access private
+ */
+ private function jsonNameValue($name, $value)
+ {
+ // Encoding the $GLOBALS PHP array causes an infinite loop
+ // if the recursion is not reset here as it contains
+ // a reference to itself. This is the only way I have come up
+ // with to stop infinite recursion in this case.
+ if ('GLOBALS' == $name && is_array($value)
+ && array_key_exists('GLOBALS', $value)) {
+ $value['GLOBALS'] = '** Recursion **';
+ }
+
+ $encoded_value = $this->jsonEncode($value);
+
+ if ($encoded_value instanceof Exception) {
+ return $encoded_value;
+ }
+
+ return $this->jsonEncode(strval($name)) . ':' . $encoded_value;
+ }
+
+ /**
+ * @deprecated
+ */
+ public function setProcessorUrl($URL)
+ {
+ trigger_error("The FirePHP::setProcessorUrl() method is no longer supported", E_USER_DEPRECATED);
+ }
+
+ /**
+ * @deprecated
+ */
+ public function setRendererUrl($URL)
+ {
+ trigger_error("The FirePHP::setRendererUrl() method is no longer supported", E_USER_DEPRECATED);
+ }
+}
diff --git a/Framework/Library/Behavior/ParseTemplateBehavior.class.php b/Framework/Library/Behavior/ParseTemplateBehavior.class.php
new file mode 100644
index 00000000..32f80579
--- /dev/null
+++ b/Framework/Library/Behavior/ParseTemplateBehavior.class.php
@@ -0,0 +1,107 @@
+
+// +----------------------------------------------------------------------
+namespace Behavior;
+
+use Think\Storage;
+use Think\Think;
+
+/**
+ * 系统行为扩展:模板解析
+ */
+class ParseTemplateBehavior
+{
+
+ // 行为扩展的执行入口必须是run
+ public function run(&$_data)
+ {
+ $engine = strtolower(C('TMPL_ENGINE_TYPE'));
+ $_content = empty($_data['content']) ? $_data['file'] : $_data['content'];
+ $_data['prefix'] = !empty($_data['prefix']) ? $_data['prefix'] : C('TMPL_CACHE_PREFIX');
+ if ('think' == $engine) {
+ // 采用Think模板引擎
+ if ((!empty($_data['content']) && $this->checkContentCache($_data['content'], $_data['prefix']))
+ || $this->checkCache($_data['file'], $_data['prefix'])) {
+ // 缓存有效
+ //载入模版缓存文件
+ Storage::load(C('CACHE_PATH') . $_data['prefix'] . md5($_content) . C('TMPL_CACHFILE_SUFFIX'), $_data['var']);
+ } else {
+ $tpl = Think::instance('Think\\Template');
+ // 编译并加载模板文件
+ $tpl->fetch($_content, $_data['var'], $_data['prefix']);
+ }
+ } else {
+ // 调用第三方模板引擎解析和输出
+ if (strpos($engine, '\\')) {
+ $class = $engine;
+ } else {
+ $class = 'Think\\Template\\Driver\\' . ucwords($engine);
+ }
+ if (class_exists($class)) {
+ $tpl = new $class;
+ $tpl->fetch($_content, $_data['var']);
+ } else {
+ // 类没有定义
+ E(L('_NOT_SUPPORT_') . ': ' . $class);
+ }
+ }
+ }
+
+ /**
+ * 检查缓存文件是否有效
+ * 如果无效则需要重新编译
+ * @access public
+ * @param string $tmplTemplateFile 模板文件名
+ * @return boolean
+ */
+ protected function checkCache($tmplTemplateFile, $prefix = '')
+ {
+ if (!C('TMPL_CACHE_ON')) // 优先对配置设定检测
+ {
+ return false;
+ }
+
+ $tmplCacheFile = C('CACHE_PATH') . $prefix . md5($tmplTemplateFile) . C('TMPL_CACHFILE_SUFFIX');
+ if (!Storage::has($tmplCacheFile)) {
+ return false;
+ } elseif (filemtime($tmplTemplateFile) > Storage::get($tmplCacheFile, 'mtime')) {
+ // 模板文件如果有更新则缓存需要更新
+ return false;
+ } elseif (C('TMPL_CACHE_TIME') != 0 && time() > Storage::get($tmplCacheFile, 'mtime') + C('TMPL_CACHE_TIME')) {
+ // 缓存是否在有效期
+ return false;
+ }
+ // 开启布局模板
+ if (C('LAYOUT_ON')) {
+ $layoutFile = THEME_PATH . C('LAYOUT_NAME') . C('TMPL_TEMPLATE_SUFFIX');
+ if (filemtime($layoutFile) > Storage::get($tmplCacheFile, 'mtime')) {
+ return false;
+ }
+ }
+ // 缓存有效
+ return true;
+ }
+
+ /**
+ * 检查缓存内容是否有效
+ * 如果无效则需要重新编译
+ * @access public
+ * @param string $tmplContent 模板内容
+ * @return boolean
+ */
+ protected function checkContentCache($tmplContent, $prefix = '')
+ {
+ if (Storage::has(C('CACHE_PATH') . $prefix . md5($tmplContent) . C('TMPL_CACHFILE_SUFFIX'))) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/Framework/Library/Behavior/ReadHtmlCacheBehavior.class.php b/Framework/Library/Behavior/ReadHtmlCacheBehavior.class.php
new file mode 100644
index 00000000..cb05c44a
--- /dev/null
+++ b/Framework/Library/Behavior/ReadHtmlCacheBehavior.class.php
@@ -0,0 +1,134 @@
+
+// +----------------------------------------------------------------------
+namespace Behavior;
+
+use Think\Storage;
+
+/**
+ * 系统行为扩展:静态缓存读取
+ */
+class ReadHtmlCacheBehavior
+{
+ // 行为扩展的执行入口必须是run
+ public function run(&$params)
+ {
+ // 开启静态缓存
+ if (IS_GET && C('HTML_CACHE_ON')) {
+ $cacheTime = $this->requireHtmlCache();
+ if (false !== $cacheTime && $this->checkHTMLCache(HTML_FILE_NAME, $cacheTime)) {
+ //静态页面有效
+ // 读取静态页面输出
+ echo Storage::read(HTML_FILE_NAME, 'html');
+ exit();
+ }
+ }
+ }
+
+ // 判断是否需要静态缓存
+ private static function requireHtmlCache()
+ {
+ // 分析当前的静态规则
+ $htmls = C('HTML_CACHE_RULES'); // 读取静态规则
+ if (!empty($htmls)) {
+ $htmls = array_change_key_case($htmls);
+ // 静态规则文件定义格式 actionName=>array('静态规则','缓存时间','附加规则')
+ // 'read'=>array('{id},{name}',60,'md5') 必须保证静态规则的唯一性 和 可判断性
+ // 检测静态规则
+ $controllerName = strtolower(CONTROLLER_NAME);
+ $actionName = strtolower(ACTION_NAME);
+ if (isset($htmls[$controllerName . ':' . $actionName])) {
+ $html = $htmls[$controllerName . ':' . $actionName]; // 某个控制器的操作的静态规则
+ } elseif (isset($htmls[$controllerName . ':'])) {
+// 某个控制器的静态规则
+ $html = $htmls[$controllerName . ':'];
+ } elseif (isset($htmls[$actionName])) {
+ $html = $htmls[$actionName]; // 所有操作的静态规则
+ } elseif (isset($htmls['*'])) {
+ $html = $htmls['*']; // 全局静态规则
+ }
+ if (!empty($html)) {
+ // 解读静态规则
+ $rule = is_array($html) ? $html[0] : $html;
+ // 以$_开头的系统变量
+ $callback = function ($match) {
+ switch ($match[1]) {
+ case '_GET':$var = $_GET[$match[2]];
+ break;
+ case '_POST':$var = $_POST[$match[2]];
+ break;
+ case '_REQUEST':$var = $_REQUEST[$match[2]];
+ break;
+ case '_SERVER':$var = $_SERVER[$match[2]];
+ break;
+ case '_SESSION':$var = $_SESSION[$match[2]];
+ break;
+ case '_COOKIE':$var = $_COOKIE[$match[2]];
+ break;
+ }
+ return (count($match) == 4) ? $match[3]($var) : $var;
+ };
+ $rule = preg_replace_callback('/{\$(_\w+)\.(\w+)(?:\|(\w+))?}/', $callback, $rule);
+ // {ID|FUN} GET变量的简写
+ $rule = preg_replace_callback('/{(\w+)\|(\w+)}/', function ($match) {return $match[2]($_GET[$match[1]]);}, $rule);
+ $rule = preg_replace_callback('/{(\w+)}/', function ($match) {return $_GET[$match[1]];}, $rule);
+ // 特殊系统变量
+ $rule = str_ireplace(
+ array('{:controller}', '{:action}', '{:module}'),
+ array(CONTROLLER_NAME, ACTION_NAME, MODULE_NAME),
+ $rule);
+ // {|FUN} 单独使用函数
+ $rule = preg_replace_callback('/{|(\w+)}/', function ($match) {return $match[1]();}, $rule);
+ $cacheTime = C('HTML_CACHE_TIME', null, 60);
+ if (is_array($html)) {
+ if (!empty($html[2])) {
+ $rule = $html[2]($rule);
+ }
+ // 应用附加函数
+ $cacheTime = isset($html[1]) ? $html[1] : $cacheTime; // 缓存有效期
+ } else {
+ $cacheTime = $cacheTime;
+ }
+
+ // 当前缓存文件
+ define('HTML_FILE_NAME', HTML_PATH . $rule . C('HTML_FILE_SUFFIX', null, '.html'));
+ return $cacheTime;
+ }
+ }
+ // 无需缓存
+ return false;
+ }
+
+ /**
+ * 检查静态HTML文件是否有效
+ * 如果无效需要重新更新
+ * @access public
+ * @param string $cacheFile 静态文件名
+ * @param integer $cacheTime 缓存有效期
+ * @return boolean
+ */
+ public static function checkHTMLCache($cacheFile = '', $cacheTime = '')
+ {
+ if (!is_file($cacheFile) && 'sae' != APP_MODE) {
+ return false;
+ } elseif (filemtime(\Think\Think::instance('Think\View')->parseTemplate()) > Storage::get($cacheFile, 'mtime', 'html')) {
+ // 模板文件如果更新静态文件需要更新
+ return false;
+ } elseif (!is_numeric($cacheTime) && function_exists($cacheTime)) {
+ return $cacheTime($cacheFile);
+ } elseif (0 != $cacheTime && NOW_TIME > Storage::get($cacheFile, 'mtime', 'html') + $cacheTime) {
+ // 文件是否在有效期
+ return false;
+ }
+ //静态文件有效
+ return true;
+ }
+
+}
diff --git a/Framework/Library/Behavior/RobotCheckBehavior.class.php b/Framework/Library/Behavior/RobotCheckBehavior.class.php
new file mode 100644
index 00000000..cf698ee1
--- /dev/null
+++ b/Framework/Library/Behavior/RobotCheckBehavior.class.php
@@ -0,0 +1,45 @@
+
+// +----------------------------------------------------------------------
+namespace Behavior;
+
+/**
+ * 机器人检测
+ * @author liu21st
+ */
+class RobotCheckBehavior
+{
+
+ public function run(&$params)
+ {
+ // 机器人访问检测
+ if (C('LIMIT_ROBOT_VISIT', null, true) && self::isRobot()) {
+ // 禁止机器人访问
+ exit('Access Denied');
+ }
+ }
+
+ private static function isRobot()
+ {
+ static $_robot = null;
+ if (is_null($_robot)) {
+ $spiders = 'Bot|Crawl|Spider|slurp|sohu-search|lycos|robozilla';
+ $browsers = 'MSIE|Netscape|Opera|Konqueror|Mozilla';
+ if (preg_match("/($browsers)/", $_SERVER['HTTP_USER_AGENT'])) {
+ $_robot = false;
+ } elseif (preg_match("/($spiders)/", $_SERVER['HTTP_USER_AGENT'])) {
+ $_robot = true;
+ } else {
+ $_robot = false;
+ }
+ }
+ return $_robot;
+ }
+}
diff --git a/Framework/Library/Behavior/ShowPageTraceBehavior.class.php b/Framework/Library/Behavior/ShowPageTraceBehavior.class.php
new file mode 100644
index 00000000..8ee71212
--- /dev/null
+++ b/Framework/Library/Behavior/ShowPageTraceBehavior.class.php
@@ -0,0 +1,125 @@
+
+// +----------------------------------------------------------------------
+namespace Behavior;
+
+/**
+ * 系统行为扩展:页面Trace显示输出
+ */
+class ShowPageTraceBehavior
+{
+ protected $tracePageTabs = array('BASE' => '基本', 'FILE' => '文件', 'INFO' => '流程', 'ERR|NOTIC' => '错误', 'SQL' => 'SQL', 'DEBUG' => '调试');
+
+ // 行为扩展的执行入口必须是run
+ public function run(&$params)
+ {
+ if (!IS_AJAX && !IS_CLI && C('SHOW_PAGE_TRACE')) {
+ echo $this->showTrace();
+ }
+ }
+
+ /**
+ * 显示页面Trace信息
+ * @access private
+ */
+ private function showTrace()
+ {
+ // 系统默认显示信息
+ $files = get_included_files();
+ $info = array();
+ foreach ($files as $key => $file) {
+ $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )';
+ }
+ $trace = array();
+ $base = array(
+ '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . __SELF__,
+ '运行时间' => $this->showTime(),
+ '吞吐率' => number_format(1 / G('beginTime', 'viewEndTime'), 2) . 'req/s',
+ '内存开销' => MEMORY_LIMIT_ON ? number_format((memory_get_usage() - $GLOBALS['_startUseMems']) / 1024, 2) . ' kb' : '不支持',
+ '查询信息' => N('db_query') . ' queries ' . N('db_write') . ' writes ',
+ '文件加载' => count(get_included_files()),
+ '缓存信息' => N('cache_read') . ' gets ' . N('cache_write') . ' writes ',
+ '配置加载' => count(C()),
+ '会话信息' => 'SESSION_ID=' . session_id(),
+ );
+ // 读取应用定义的Trace文件
+ $traceFile = COMMON_PATH . 'Conf/trace.php';
+ if (is_file($traceFile)) {
+ $base = array_merge($base, include $traceFile);
+ }
+ $debug = trace();
+ $tabs = C('TRACE_PAGE_TABS', null, $this->tracePageTabs);
+ foreach ($tabs as $name => $title) {
+ switch (strtoupper($name)) {
+ case 'BASE': // 基本信息
+ $trace[$title] = $base;
+ break;
+ case 'FILE': // 文件信息
+ $trace[$title] = $info;
+ break;
+ default: // 调试信息
+ $name = strtoupper($name);
+ if (strpos($name, '|')) {
+// 多组信息
+ $names = explode('|', $name);
+ $result = array();
+ foreach ($names as $name) {
+ $result += isset($debug[$name]) ? $debug[$name] : array();
+ }
+ $trace[$title] = $result;
+ } else {
+ $trace[$title] = isset($debug[$name]) ? $debug[$name] : '';
+ }
+ }
+ }
+ if ($save = C('PAGE_TRACE_SAVE')) {
+ // 保存页面Trace日志
+ if (is_array($save)) { // 选择选项卡保存
+ $tabs = C('TRACE_PAGE_TABS', null, $this->tracePageTabs);
+ $array = array();
+ foreach ($save as $tab) {
+ $array[] = $tabs[$tab];
+ }
+ }
+ $content = date('[ c ]') . ' ' . get_client_ip() . ' ' . $_SERVER['REQUEST_URI'] . "\r\n";
+ foreach ($trace as $key => $val) {
+ if (!isset($array) || in_array_case($key, $array)) {
+ $content .= '[ ' . $key . " ]\r\n";
+ if (is_array($val)) {
+ foreach ($val as $k => $v) {
+ $content .= (!is_numeric($k) ? $k . ':' : '') . print_r($v, true) . "\r\n";
+ }
+ } else {
+ $content .= print_r($val, true) . "\r\n";
+ }
+ $content .= "\r\n";
+ }
+ }
+ error_log(str_replace(' ', "\r\n", $content), 3, C('LOG_PATH') . date('y_m_d') . '_trace.log');
+ }
+ unset($files, $info, $base);
+ // 调用Trace页面模板
+ ob_start();
+ include C('TMPL_TRACE_FILE') ? C('TMPL_TRACE_FILE') : THINK_PATH . 'Tpl/page_trace.tpl';
+ return ob_get_clean();
+ }
+
+ /**
+ * 获取运行时间
+ */
+ private function showTime()
+ {
+ // 显示运行时间
+ G('beginTime', $GLOBALS['_beginTime']);
+ G('viewEndTime');
+ // 显示详细运行时间
+ return G('beginTime', 'viewEndTime') . 's ( Load:' . G('beginTime', 'loadTime') . 's Init:' . G('loadTime', 'initTime') . 's Exec:' . G('initTime', 'viewStartTime') . 's Template:' . G('viewStartTime', 'viewEndTime') . 's )';
+ }
+}
diff --git a/Framework/Library/Behavior/ShowRuntimeBehavior.class.php b/Framework/Library/Behavior/ShowRuntimeBehavior.class.php
new file mode 100644
index 00000000..6f62361d
--- /dev/null
+++ b/Framework/Library/Behavior/ShowRuntimeBehavior.class.php
@@ -0,0 +1,75 @@
+
+// +----------------------------------------------------------------------
+namespace Behavior;
+
+/**
+ * 系统行为扩展:运行时间信息显示
+ */
+class ShowRuntimeBehavior
+{
+
+ // 行为扩展的执行入口必须是run
+ public function run(&$content)
+ {
+ if (C('SHOW_RUN_TIME')) {
+ if (false !== strpos($content, '{__NORUNTIME__}')) {
+ $content = str_replace('{__NORUNTIME__}', '', $content);
+ } else {
+ $runtime = $this->showTime();
+ if (strpos($content, '{__RUNTIME__}')) {
+ $content = str_replace('{__RUNTIME__}', $runtime, $content);
+ } else {
+ $content .= $runtime;
+ }
+
+ }
+ } else {
+ $content = str_replace(array('{__NORUNTIME__}', '{__RUNTIME__}'), '', $content);
+ }
+ }
+
+ /**
+ * 显示运行时间、数据库操作、缓存次数、内存使用信息
+ * @access private
+ * @return string
+ */
+ private function showTime()
+ {
+ // 显示运行时间
+ G('beginTime', $GLOBALS['_beginTime']);
+ G('viewEndTime');
+ $showTime = 'Process: ' . G('beginTime', 'viewEndTime') . 's ';
+ if (C('SHOW_ADV_TIME')) {
+ // 显示详细运行时间
+ $showTime .= '( Load:' . G('beginTime', 'loadTime') . 's Init:' . G('loadTime', 'initTime') . 's Exec:' . G('initTime', 'viewStartTime') . 's Template:' . G('viewStartTime', 'viewEndTime') . 's )';
+ }
+ if (C('SHOW_DB_TIMES')) {
+ // 显示数据库操作次数
+ $showTime .= ' | DB :' . N('db_query') . ' queries ' . N('db_write') . ' writes ';
+ }
+ if (C('SHOW_CACHE_TIMES')) {
+ // 显示缓存读写次数
+ $showTime .= ' | Cache :' . N('cache_read') . ' gets ' . N('cache_write') . ' writes ';
+ }
+ if (MEMORY_LIMIT_ON && C('SHOW_USE_MEM')) {
+ // 显示内存开销
+ $showTime .= ' | UseMem:' . number_format((memory_get_usage() - $GLOBALS['_startUseMems']) / 1024) . ' kb';
+ }
+ if (C('SHOW_LOAD_FILE')) {
+ $showTime .= ' | LoadFile:' . count(get_included_files());
+ }
+ if (C('SHOW_FUN_TIMES')) {
+ $fun = get_defined_functions();
+ $showTime .= ' | CallFun:' . count($fun['user']) . ',' . count($fun['internal']);
+ }
+ return $showTime;
+ }
+}
diff --git a/Framework/Library/Behavior/TokenBuildBehavior.class.php b/Framework/Library/Behavior/TokenBuildBehavior.class.php
new file mode 100644
index 00000000..796ecfad
--- /dev/null
+++ b/Framework/Library/Behavior/TokenBuildBehavior.class.php
@@ -0,0 +1,61 @@
+
+// +----------------------------------------------------------------------
+namespace Behavior;
+
+/**
+ * 系统行为扩展:表单令牌生成
+ */
+class TokenBuildBehavior
+{
+
+ public function run(&$content)
+ {
+ if (C('TOKEN_ON')) {
+ list($tokenName, $tokenKey, $tokenValue) = $this->getToken();
+ $input_token = ' ';
+ $meta_token = ' ';
+ if (strpos($content, '{__TOKEN__}')) {
+ // 指定表单令牌隐藏域位置
+ $content = str_replace('{__TOKEN__}', $input_token, $content);
+ } elseif (preg_match('/<\/form(\s*)>/is', $content, $match)) {
+ // 智能生成表单令牌隐藏域
+ $content = str_replace($match[0], $input_token . $match[0], $content);
+ }
+ $content = str_ireplace('', $meta_token . '', $content);
+ } else {
+ $content = str_replace('{__TOKEN__}', '', $content);
+ }
+ }
+
+ //获得token
+ private function getToken()
+ {
+ $tokenName = C('TOKEN_NAME', null, '__hash__');
+ $tokenType = C('TOKEN_TYPE', null, 'md5');
+ if (!isset($_SESSION[$tokenName])) {
+ $_SESSION[$tokenName] = array();
+ }
+ // 标识当前页面唯一性
+ $tokenKey = md5($_SERVER['REQUEST_URI']);
+ if (isset($_SESSION[$tokenName][$tokenKey])) {
+// 相同页面不重复生成session
+ $tokenValue = $_SESSION[$tokenName][$tokenKey];
+ } else {
+ $tokenValue = is_callable($tokenType) ? $tokenType(microtime(true)) : md5(microtime(true));
+ $_SESSION[$tokenName][$tokenKey] = $tokenValue;
+ if (IS_AJAX && C('TOKEN_RESET', null, true)) {
+ header($tokenName . ': ' . $tokenKey . '_' . $tokenValue);
+ }
+ //ajax需要获得这个header并替换页面中meta中的token值
+ }
+ return array($tokenName, $tokenKey, $tokenValue);
+ }
+}
diff --git a/Framework/Library/Behavior/UpgradeNoticeBehavior.class.php b/Framework/Library/Behavior/UpgradeNoticeBehavior.class.php
new file mode 100644
index 00000000..0a191de6
--- /dev/null
+++ b/Framework/Library/Behavior/UpgradeNoticeBehavior.class.php
@@ -0,0 +1,128 @@
+
+// +----------------------------------------------------------------------
+namespace Behavior;
+
+/**
+ * 升级短信通知, 如果有ThinkPHP新版升级,或者重要的更新,会发送短信通知你。
+ * 需要使用SAE的短信服务。请先找一个SAE的应用开通短信服务。
+ * 使用步骤如下:
+ * 1,在项目的Conf目录下建立tags.php配置文件,内容如下:
+ *
+ * array('UpgradeNotice')
+ * );
+ *
+ *
+ * 2,将此文件放在应用的Lib/Behavior文件夹下。
+ *注:在SAE上面使用时,以上两步可以省略
+ * 3,在config.php中配置:
+ * 'UPGRADE_NOTICE_ON'=>true,//开启短信升级提醒功能
+ * 'UPGRADE_NOTICE_AKEY'=>'your akey',//SAE应用的AKEY,如果在SAE上使用可以不填
+ * 'UPGRADE_NOTICE_SKEY'=>'your skey',//SAE应用的SKEY,如果在SAE上使用可以不填
+ *'UPGRADE_NOTICE_MOBILE'=>'136456789',//接受短信的手机号
+ *'UPGRADE_NOTICE_CHECK_INTERVAL' => 604800,//检测频率,单位秒,默认是一周
+ *'UPGRADE_CURRENT_VERSION'=>'0',//升级后的版本号,会在短信中告诉你填写什么
+ *UPGRADE_NOTICE_DEBUG=>true, //调试默认,如果为true,UPGRADE_NOTICE_CHECK_INTERVAL配置不起作用,每次都会进行版本检查,此时用于调试,调试完毕后请设置次配置为false
+ *
+ */
+
+class UpgradeNoticeBehavior
+{
+
+ protected $header_ = '';
+ protected $httpCode_;
+ protected $httpDesc_;
+ protected $accesskey_;
+ protected $secretkey_;
+ public function run(&$params)
+ {
+ if (C('UPGRADE_NOTICE_ON') && (!S('think_upgrade_interval') || C('UPGRADE_NOTICE_DEBUG'))) {
+ if (IS_SAE && C('UPGRADE_NOTICE_QUEUE') && !isset($_POST['think_upgrade_queque'])) {
+ $queue = new SaeTaskQueue(C('UPGRADE_NOTICE_QUEUE'));
+ $queue->addTask('http://' . $_SERVER['HTTP_HOST'] . __APP__, 'think_upgrade_queque=1');
+ if (!$queue->push()) {
+ trace('升级提醒队列执行失败,错误原因:' . $queue->errmsg(), '升级通知出错', 'NOTIC', true);
+ }
+ return;
+ }
+ $akey = C('UPGRADE_NOTICE_AKEY', null, '');
+ $skey = C('UPGRADE_NOTICE_SKEY', null, '');
+ $this->accesskey_ = $akey ? $akey : (defined('SAE_ACCESSKEY') ? SAE_ACCESSKEY : '');
+ $this->secretkey_ = $skey ? $skey : (defined('SAE_SECRETKEY') ? SAE_SECRETKEY : '');
+ $current_version = C('UPGRADE_CURRENT_VERSION', null, 0);
+ //读取接口
+ $info = $this->send('http://sinaclouds.sinaapp.com/thinkapi/upgrade.php?v=' . $current_version);
+ if ($info['version'] != $current_version) {
+ if ($this->sendSms($info['msg'])) {
+ trace($info['msg'], '升级通知成功', 'NOTIC', true);
+ }
+ //发送升级短信
+ }
+ S('think_upgrade_interval', true, C('UPGRADE_NOTICE_CHECK_INTERVAL', null, 604800));
+ }
+ }
+ private function sendSms($msg)
+ {
+ $timestamp = time();
+ $url = 'http://inno.smsinter.sina.com.cn/sae_sms_service/sendsms.php'; //发送短信的接口地址
+ $content = "FetchUrl" . $url . "TimeStamp" . $timestamp . "AccessKey" . $this->accesskey_;
+ $signature = (base64_encode(hash_hmac('sha256', $content, $this->secretkey_, true)));
+ $headers = array(
+ "FetchUrl: $url",
+ "AccessKey: " . $this->accesskey_,
+ "TimeStamp: " . $timestamp,
+ "Signature: $signature",
+ );
+ $data = array(
+ 'mobile' => C('UPGRADE_NOTICE_MOBILE', null, ''),
+ 'msg' => $msg,
+ 'encoding' => 'UTF-8',
+ );
+ if (!$ret = $this->send('http://g.apibus.io', $data, $headers)) {
+ return false;
+ }
+ if (isset($ret['ApiBusError'])) {
+ trace('errno:' . $ret['ApiBusError']['errcode'] . ',errmsg:' . $ret['ApiBusError']['errdesc'], '升级通知出错', 'NOTIC', true);
+
+ return false;
+ }
+
+ return true;
+ }
+ private function send($url, $params = array(), $headers = array())
+ {
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ if (!empty($params)) {
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
+ }
+ if (!empty($headers)) {
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+ }
+
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ $txt = curl_exec($ch);
+ if (curl_errno($ch)) {
+ trace(curl_error($ch), '升级通知出错', 'NOTIC', true);
+
+ return false;
+ }
+ curl_close($ch);
+ $ret = json_decode($txt, true);
+ if (!$ret) {
+ trace('接口[' . $url . ']返回格式不正确', '升级通知出错', 'NOTIC', true);
+
+ return false;
+ }
+
+ return $ret;
+ }
+}
diff --git a/Framework/Library/Behavior/WriteHtmlCacheBehavior.class.php b/Framework/Library/Behavior/WriteHtmlCacheBehavior.class.php
new file mode 100644
index 00000000..a9d5a0be
--- /dev/null
+++ b/Framework/Library/Behavior/WriteHtmlCacheBehavior.class.php
@@ -0,0 +1,33 @@
+
+// +----------------------------------------------------------------------
+namespace Behavior;
+
+use Think\Storage;
+
+/**
+ * 系统行为扩展:静态缓存写入
+ */
+class WriteHtmlCacheBehavior
+{
+
+ // 行为扩展的执行入口必须是run
+ public function run(&$content)
+ {
+ //2014-11-28 修改 如果有HTTP 4xx 3xx 5xx 头部,禁止存储
+ //2014-12-1 修改 对注入的网址 防止生成,例如 /game/lst/SortType/hot/-e8-90-8c-e5-85-94-e7-88-b1-e6-b6-88-e9-99-a4/-e8-bf-9b-e5-87-bb-e7-9a-84-e9-83-a8-e8-90-bd/-e9-a3-8e-e4-ba-91-e5-a4-a9-e4-b8-8b/index.shtml
+ if (C('HTML_CACHE_ON') && defined('HTML_FILE_NAME')
+ && !preg_match('/Status.*[345]{1}\d{2}/i', implode(' ', headers_list()))
+ && !preg_match('/(-[a-z0-9]{2}){3,}/i', HTML_FILE_NAME)) {
+ //静态文件写入
+ Storage::put(HTML_FILE_NAME, $content, 'html');
+ }
+ }
+}
diff --git a/Framework/Library/Org/Net/Http.class.php b/Framework/Library/Org/Net/Http.class.php
new file mode 100644
index 00000000..3e301071
--- /dev/null
+++ b/Framework/Library/Org/Net/Http.class.php
@@ -0,0 +1,289 @@
+
+// +----------------------------------------------------------------------
+namespace Org\Net;
+
+/**
+ * Http 工具类
+ * 提供一系列的Http方法
+ * @author liu21st
+ */
+class Http
+{
+
+ /**
+ * 采集远程文件
+ * @access public
+ * @param string $remote 远程文件名
+ * @param string $local 本地保存文件名
+ * @return mixed
+ */
+ public static function curlDownload($remote, $local)
+ {
+ $cp = curl_init($remote);
+ $fp = fopen($local, "w");
+ curl_setopt($cp, CURLOPT_FILE, $fp);
+ curl_setopt($cp, CURLOPT_HEADER, 0);
+ curl_exec($cp);
+ curl_close($cp);
+ fclose($fp);
+ }
+
+ /**
+ * 使用 fsockopen 通过 HTTP 协议直接访问(采集)远程文件
+ * 如果主机或服务器没有开启 CURL 扩展可考虑使用
+ * fsockopen 比 CURL 稍慢,但性能稳定
+ * @static
+ * @access public
+ * @param string $url 远程URL
+ * @param array $conf 其他配置信息
+ * int limit 分段读取字符个数
+ * string post post的内容,字符串或数组,key=value&形式
+ * string cookie 携带cookie访问,该参数是cookie内容
+ * string ip 如果该参数传入,$url将不被使用,ip访问优先
+ * int timeout 采集超时时间
+ * bool block 是否阻塞访问,默认为true
+ * @return mixed
+ */
+ public static function fsockopenDownload($url, $conf = array())
+ {
+ $return = '';
+ if (!is_array($conf)) {
+ return $return;
+ }
+
+ $matches = parse_url($url);
+ !isset($matches['host']) && $matches['host'] = '';
+ !isset($matches['path']) && $matches['path'] = '';
+ !isset($matches['query']) && $matches['query'] = '';
+ !isset($matches['port']) && $matches['port'] = '';
+ $host = $matches['host'];
+ $path = $matches['path'] ? $matches['path'] . ($matches['query'] ? '?' . $matches['query'] : '') : '/';
+ $port = !empty($matches['port']) ? $matches['port'] : 80;
+
+ $conf_arr = array(
+ 'limit' => 0,
+ 'post' => '',
+ 'cookie' => '',
+ 'ip' => '',
+ 'timeout' => 15,
+ 'block' => true,
+ );
+
+ foreach (array_merge($conf_arr, $conf) as $k => $v) {
+ ${$k} = $v;
+ }
+
+ if ($post) {
+ if (is_array($post)) {
+ $post = http_build_query($post);
+ }
+ $out = "POST $path HTTP/1.0\r\n";
+ $out .= "Accept: */*\r\n";
+ //$out .= "Referer: $boardurl\r\n";
+ $out .= "Accept-Language: zh-cn\r\n";
+ $out .= "Content-Type: application/x-www-form-urlencoded\r\n";
+ $out .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n";
+ $out .= "Host: $host\r\n";
+ $out .= 'Content-Length: ' . strlen($post) . "\r\n";
+ $out .= "Connection: Close\r\n";
+ $out .= "Cache-Control: no-cache\r\n";
+ $out .= "Cookie: $cookie\r\n\r\n";
+ $out .= $post;
+ } else {
+ $out = "GET $path HTTP/1.0\r\n";
+ $out .= "Accept: */*\r\n";
+ //$out .= "Referer: $boardurl\r\n";
+ $out .= "Accept-Language: zh-cn\r\n";
+ $out .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n";
+ $out .= "Host: $host\r\n";
+ $out .= "Connection: Close\r\n";
+ $out .= "Cookie: $cookie\r\n\r\n";
+ }
+ $fp = @fsockopen(($ip ? $ip : $host), $port, $errno, $errstr, $timeout);
+ if (!$fp) {
+ return '';
+ } else {
+ stream_set_blocking($fp, $block);
+ stream_set_timeout($fp, $timeout);
+ @fwrite($fp, $out);
+ $status = stream_get_meta_data($fp);
+ if (!$status['timed_out']) {
+ while (!feof($fp)) {
+ if (($header = @fgets($fp)) && ("\r\n" == $header || "\n" == $header)) {
+ break;
+ }
+ }
+
+ $stop = false;
+ while (!feof($fp) && !$stop) {
+ $data = fread($fp, (0 == $limit || $limit > 8192 ? 8192 : $limit));
+ $return .= $data;
+ if ($limit) {
+ $limit -= strlen($data);
+ $stop = $limit <= 0;
+ }
+ }
+ }
+ @fclose($fp);
+ return $return;
+ }
+ }
+
+ /**
+ * 下载文件
+ * 可以指定下载显示的文件名,并自动发送相应的Header信息
+ * 如果指定了content参数,则下载该参数的内容
+ * @static
+ * @access public
+ * @param string $filename 下载文件名
+ * @param string $showname 下载显示的文件名
+ * @param string $content 下载的内容
+ * @param integer $expire 下载内容浏览器缓存时间
+ * @return void
+ */
+ public static function download($filename, $showname = '', $content = '', $expire = 180)
+ {
+ if (is_file($filename)) {
+ $length = filesize($filename);
+ } elseif (is_file(UPLOAD_PATH . $filename)) {
+ $filename = UPLOAD_PATH . $filename;
+ $length = filesize($filename);
+ } elseif ('' != $content) {
+ $length = strlen($content);
+ } else {
+ E($filename . L('下载文件不存在!'));
+ }
+ if (empty($showname)) {
+ $showname = $filename;
+ }
+ $showname = self::get_basename($showname);;
+ if (!empty($filename)) {
+ $finfo = new \finfo(FILEINFO_MIME);
+ $type = $finfo->file($filename);
+ } else {
+ $type = "application/octet-stream";
+ }
+ //发送Http Header信息 开始下载
+ header("Pragma: public");
+ header("Cache-control: max-age=" . $expire);
+ //header('Cache-Control: no-store, no-cache, must-revalidate');
+ header("Expires: " . gmdate("D, d M Y H:i:s", time() + $expire) . "GMT");
+ header("Last-Modified: " . gmdate("D, d M Y H:i:s", time()) . "GMT");
+ header("Content-Disposition: attachment; filename=" . $showname);
+ header("Content-Length: " . $length);
+ header("Content-type: " . $type);
+ header('Content-Encoding: none');
+ header("Content-Transfer-Encoding: binary");
+ if ('' == $content) {
+ readfile($filename);
+ } else {
+ echo ($content);
+ }
+ exit();
+ }
+
+ /**
+ * 获取文件的名称,兼容中文名
+ * @return string
+ */
+ static public function get_basename($filename){
+ return preg_replace('/^.+[\\\\\\/]/', '', $filename);
+ }
+
+ /**
+ * 显示HTTP Header 信息
+ * @return string
+ */
+ public static function getHeaderInfo($header = '', $echo = true)
+ {
+ ob_start();
+ $headers = getallheaders();
+ if (!empty($header)) {
+ $info = $headers[$header];
+ echo ($header . ':' . $info . "\n");
+ } else {
+ foreach ($headers as $key => $val) {
+ echo ("$key:$val\n");
+ }
+ }
+ $output = ob_get_clean();
+ if ($echo) {
+ echo (nl2br($output));
+ } else {
+ return $output;
+ }
+
+ }
+
+ /**
+ * HTTP Protocol defined status codes
+ * @param int $num
+ */
+ public static function sendHttpStatus($code)
+ {
+ static $_status = array(
+ // Informational 1xx
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+
+ // Success 2xx
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+
+ // Redirection 3xx
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found', // 1.1
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ // 306 is deprecated but reserved
+ 307 => 'Temporary Redirect',
+
+ // Client Error 4xx
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+
+ // Server Error 5xx
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ 509 => 'Bandwidth Limit Exceeded',
+ );
+ if (isset($_status[$code])) {
+ header('HTTP/1.1 ' . $code . ' ' . $_status[$code]);
+ }
+ }
+} //类定义结束
diff --git a/Framework/Library/Org/Net/IpLocation.class.php b/Framework/Library/Org/Net/IpLocation.class.php
new file mode 100644
index 00000000..8d8562e6
--- /dev/null
+++ b/Framework/Library/Org/Net/IpLocation.class.php
@@ -0,0 +1,254 @@
+
+// +----------------------------------------------------------------------
+namespace Org\Net;
+
+/**
+ * IP 地理位置查询类 修改自 CoolCode.CN
+ * 由于使用UTF8编码 如果使用纯真IP地址库的话 需要对返回结果进行编码转换
+ * @author liu21st
+ */
+class IpLocation
+{
+ /**
+ * QQWry.Dat文件指针
+ *
+ * @var resource
+ */
+ private $fp;
+
+ /**
+ * 第一条IP记录的偏移地址
+ *
+ * @var int
+ */
+ private $firstip;
+
+ /**
+ * 最后一条IP记录的偏移地址
+ *
+ * @var int
+ */
+ private $lastip;
+
+ /**
+ * IP记录的总条数(不包含版本信息记录)
+ *
+ * @var int
+ */
+ private $totalip;
+
+ /**
+ * 构造函数,打开 QQWry.Dat 文件并初始化类中的信息
+ *
+ * @param string $filename
+ * @return IpLocation
+ */
+ public function __construct($filename = "UTFWry.dat")
+ {
+ $this->fp = 0;
+ if(!is_file($filename)) {
+ $filename = dirname(__FILE__) . '/' . $filename;
+ }
+ if (($this->fp = fopen($filename, 'rb')) !== false) {
+ $this->firstip = $this->getlong();
+ $this->lastip = $this->getlong();
+ $this->totalip = ($this->lastip - $this->firstip) / 7;
+ }
+ }
+
+ /**
+ * 返回读取的长整型数
+ *
+ * @access private
+ * @return int
+ */
+ private function getlong()
+ {
+ //将读取的little-endian编码的4个字节转化为长整型数
+ $result = unpack('Vlong', fread($this->fp, 4));
+ return $result['long'];
+ }
+
+ /**
+ * 返回读取的3个字节的长整型数
+ *
+ * @access private
+ * @return int
+ */
+ private function getlong3()
+ {
+ //将读取的little-endian编码的3个字节转化为长整型数
+ $result = unpack('Vlong', fread($this->fp, 3) . chr(0));
+ return $result['long'];
+ }
+
+ /**
+ * 返回压缩后可进行比较的IP地址
+ *
+ * @access private
+ * @param string $ip
+ * @return string
+ */
+ private function packip($ip)
+ {
+ // 将IP地址转化为长整型数,如果在PHP5中,IP地址错误,则返回False,
+ // 这时intval将Flase转化为整数-1,之后压缩成big-endian编码的字符串
+ return pack('N', intval(ip2long($ip)));
+ }
+
+ /**
+ * 返回读取的字符串
+ *
+ * @access private
+ * @param string $data
+ * @return string
+ */
+ private function getstring($data = "")
+ {
+ $char = fread($this->fp, 1);
+ while (ord($char) > 0) {
+ // 字符串按照C格式保存,以\0结束
+ $data .= $char; // 将读取的字符连接到给定字符串之后
+ $char = fread($this->fp, 1);
+ }
+ return $data;
+ }
+
+ /**
+ * 返回地区信息
+ *
+ * @access private
+ * @return string
+ */
+ private function getarea()
+ {
+ $byte = fread($this->fp, 1); // 标志字节
+ switch (ord($byte)) {
+ case 0: // 没有区域信息
+ $area = "";
+ break;
+ case 1:
+ case 2: // 标志字节为1或2,表示区域信息被重定向
+ fseek($this->fp, $this->getlong3());
+ $area = $this->getstring();
+ break;
+ default: // 否则,表示区域信息没有被重定向
+ $area = $this->getstring($byte);
+ break;
+ }
+ return $area;
+ }
+
+ /**
+ * 根据所给 IP 地址或域名返回所在地区信息
+ *
+ * @access public
+ * @param string $ip
+ * @return array
+ */
+ public function getlocation($ip = '')
+ {
+ if (!$this->fp) {
+ return null;
+ }
+ // 如果数据文件没有被正确打开,则直接返回空
+ if (empty($ip)) {
+ $ip = get_client_ip();
+ }
+
+ $location['ip'] = gethostbyname($ip); // 将输入的域名转化为IP地址
+ $ip = $this->packip($location['ip']); // 将输入的IP地址转化为可比较的IP地址
+ // 不合法的IP地址会被转化为255.255.255.255
+ // 对分搜索
+ $l = 0; // 搜索的下边界
+ $u = $this->totalip; // 搜索的上边界
+ $findip = $this->lastip; // 如果没有找到就返回最后一条IP记录(QQWry.Dat的版本信息)
+ while ($l <= $u) {
+ // 当上边界小于下边界时,查找失败
+ $i = floor(($l + $u) / 2); // 计算近似中间记录
+ fseek($this->fp, $this->firstip + $i * 7);
+ $beginip = strrev(fread($this->fp, 4)); // 获取中间记录的开始IP地址
+ // strrev函数在这里的作用是将little-endian的压缩IP地址转化为big-endian的格式
+ // 以便用于比较,后面相同。
+ if ($ip < $beginip) { // 用户的IP小于中间记录的开始IP地址时
+ $u = $i - 1; // 将搜索的上边界修改为中间记录减一
+ } else {
+ fseek($this->fp, $this->getlong3());
+ $endip = strrev(fread($this->fp, 4)); // 获取中间记录的结束IP地址
+ if ($ip > $endip) { // 用户的IP大于中间记录的结束IP地址时
+ $l = $i + 1; // 将搜索的下边界修改为中间记录加一
+ } else {
+ // 用户的IP在中间记录的IP范围内时
+ $findip = $this->firstip + $i * 7;
+ break; // 则表示找到结果,退出循环
+ }
+ }
+ }
+
+ //获取查找到的IP地理位置信息
+ fseek($this->fp, $findip);
+ $location['beginip'] = long2ip($this->getlong()); // 用户IP所在范围的开始地址
+ $offset = $this->getlong3();
+ fseek($this->fp, $offset);
+ $location['endip'] = long2ip($this->getlong()); // 用户IP所在范围的结束地址
+ $byte = fread($this->fp, 1); // 标志字节
+ switch (ord($byte)) {
+ case 1: // 标志字节为1,表示国家和区域信息都被同时重定向
+ $countryOffset = $this->getlong3(); // 重定向地址
+ fseek($this->fp, $countryOffset);
+ $byte = fread($this->fp, 1); // 标志字节
+ switch (ord($byte)) {
+ case 2: // 标志字节为2,表示国家信息又被重定向
+ fseek($this->fp, $this->getlong3());
+ $location['country'] = $this->getstring();
+ fseek($this->fp, $countryOffset + 4);
+ $location['area'] = $this->getarea();
+ break;
+ default: // 否则,表示国家信息没有被重定向
+ $location['country'] = $this->getstring($byte);
+ $location['area'] = $this->getarea();
+ break;
+ }
+ break;
+ case 2: // 标志字节为2,表示国家信息被重定向
+ fseek($this->fp, $this->getlong3());
+ $location['country'] = $this->getstring();
+ fseek($this->fp, $offset + 8);
+ $location['area'] = $this->getarea();
+ break;
+ default: // 否则,表示国家信息没有被重定向
+ $location['country'] = $this->getstring($byte);
+ $location['area'] = $this->getarea();
+ break;
+ }
+ if (trim($location['country']) == 'CZ88.NET') {
+ // CZ88.NET表示没有有效信息
+ $location['country'] = '未知';
+ }
+ if (trim($location['area']) == 'CZ88.NET') {
+ $location['area'] = '';
+ }
+ return $location;
+ }
+
+ /**
+ * 析构函数,用于在页面执行结束后自动关闭打开的文件。
+ *
+ */
+ public function __destruct()
+ {
+ if ($this->fp) {
+ fclose($this->fp);
+ }
+ $this->fp = 0;
+ }
+
+}
diff --git a/Framework/Library/Org/Util/ArrayList.class.php b/Framework/Library/Org/Util/ArrayList.class.php
new file mode 100644
index 00000000..a353e9da
--- /dev/null
+++ b/Framework/Library/Org/Util/ArrayList.class.php
@@ -0,0 +1,266 @@
+
+// +----------------------------------------------------------------------
+namespace Org\Util;
+
+/**
+ * ArrayList实现类
+ * @category Think
+ * @package Think
+ * @subpackage Util
+ * @author liu21st
+ */
+class ArrayList implements \IteratorAggregate
+{
+
+ /**
+ * 集合元素
+ * @var array
+ * @access protected
+ */
+ protected $_elements = array();
+
+ /**
+ * 架构函数
+ * @access public
+ * @param string $elements 初始化数组元素
+ */
+ public function __construct($elements = array())
+ {
+ if (!empty($elements)) {
+ $this->_elements = $elements;
+ }
+ }
+
+ /**
+ * 若要获得迭代因子,通过getIterator方法实现
+ * @access public
+ * @return ArrayObject
+ */
+ public function getIterator()
+ {
+ return new ArrayObject($this->_elements);
+ }
+
+ /**
+ * 增加元素
+ * @access public
+ * @param mixed $element 要添加的元素
+ * @return boolean
+ */
+ public function add($element)
+ {
+ return (array_push($this->_elements, $element)) ? true : false;
+ }
+
+ //
+ public function unshift($element)
+ {
+ return (array_unshift($this->_elements, $element)) ? true : false;
+ }
+
+ //
+ public function pop()
+ {
+ return array_pop($this->_elements);
+ }
+
+ /**
+ * 增加元素列表
+ * @access public
+ * @param ArrayList $list 元素列表
+ * @return boolean
+ */
+ public function addAll($list)
+ {
+ $before = $this->size();
+ foreach ($list as $element) {
+ $this->add($element);
+ }
+ $after = $this->size();
+ return ($before < $after);
+ }
+
+ /**
+ * 清除所有元素
+ * @access public
+ */
+ public function clear()
+ {
+ $this->_elements = array();
+ }
+
+ /**
+ * 是否包含某个元素
+ * @access public
+ * @param mixed $element 查找元素
+ * @return string
+ */
+ public function contains($element)
+ {
+ return (array_search($element, $this->_elements) !== false);
+ }
+
+ /**
+ * 根据索引取得元素
+ * @access public
+ * @param integer $index 索引
+ * @return mixed
+ */
+ public function get($index)
+ {
+ return $this->_elements[$index];
+ }
+
+ /**
+ * 查找匹配元素,并返回第一个元素所在位置
+ * 注意 可能存在0的索引位置 因此要用===False来判断查找失败
+ * @access public
+ * @param mixed $element 查找元素
+ * @return integer
+ */
+ public function indexOf($element)
+ {
+ return array_search($element, $this->_elements);
+ }
+
+ /**
+ * 判断元素是否为空
+ * @access public
+ * @return boolean
+ */
+ public function isEmpty()
+ {
+ return empty($this->_elements);
+ }
+
+ /**
+ * 最后一个匹配的元素位置
+ * @access public
+ * @param mixed $element 查找元素
+ * @return integer
+ */
+ public function lastIndexOf($element)
+ {
+ for ($i = (count($this->_elements) - 1); $i > 0; $i--) {
+ if ($this->get($i) == $element) {return $i;}
+ }
+ }
+
+ public function toJson()
+ {
+ return json_encode($this->_elements);
+ }
+
+ /**
+ * 根据索引移除元素
+ * 返回被移除的元素
+ * @access public
+ * @param integer $index 索引
+ * @return mixed
+ */
+ public function remove($index)
+ {
+ $element = $this->get($index);
+ if (!is_null($element)) {array_splice($this->_elements, $index, 1);}
+ return $element;
+ }
+
+ /**
+ * 移出一定范围的数组列表
+ * @access public
+ * @param integer $offset 开始移除位置
+ * @param integer $length 移除长度
+ */
+ public function removeRange($offset, $length)
+ {
+ array_splice($this->_elements, $offset, $length);
+ }
+
+ /**
+ * 移出重复的值
+ * @access public
+ */
+ public function unique()
+ {
+ $this->_elements = array_unique($this->_elements);
+ }
+
+ /**
+ * 取出一定范围的数组列表
+ * @access public
+ * @param integer $offset 开始位置
+ * @param integer $length 长度
+ */
+ public function range($offset, $length = null)
+ {
+ return array_slice($this->_elements, $offset, $length);
+ }
+
+ /**
+ * 设置列表元素
+ * 返回修改之前的值
+ * @access public
+ * @param integer $index 索引
+ * @param mixed $element 元素
+ * @return mixed
+ */
+ public function set($index, $element)
+ {
+ $previous = $this->get($index);
+ $this->_elements[$index] = $element;
+ return $previous;
+ }
+
+ /**
+ * 获取列表长度
+ * @access public
+ * @return integer
+ */
+ public function size()
+ {
+ return count($this->_elements);
+ }
+
+ /**
+ * 转换成数组
+ * @access public
+ * @return array
+ */
+ public function toArray()
+ {
+ return $this->_elements;
+ }
+
+ // 列表排序
+ public function ksort()
+ {
+ ksort($this->_elements);
+ }
+
+ // 列表排序
+ public function asort()
+ {
+ asort($this->_elements);
+ }
+
+ // 逆向排序
+ public function rsort()
+ {
+ rsort($this->_elements);
+ }
+
+ // 自然排序
+ public function natsort()
+ {
+ natsort($this->_elements);
+ }
+
+}
diff --git a/Framework/Library/Org/Util/CodeSwitch.class.php b/Framework/Library/Org/Util/CodeSwitch.class.php
new file mode 100644
index 00000000..73907b1c
--- /dev/null
+++ b/Framework/Library/Org/Util/CodeSwitch.class.php
@@ -0,0 +1,221 @@
+
+// +----------------------------------------------------------------------
+namespace Org\Util;
+
+class CodeSwitch
+{
+ // 错误信息
+ private static $error = array();
+ // 提示信息
+ private static $info = array();
+ // 记录错误
+ private static function error($msg)
+ {
+ self::$error[] = $msg;
+ }
+ // 记录信息
+ private static function info($info)
+ {
+ self::$info[] = $info;
+ }
+ /**
+ * 编码转换函数,对整个文件进行编码转换
+ * 支持以下转换
+ * GB2312、UTF-8 WITH BOM转换为UTF-8
+ * UTF-8、UTF-8 WITH BOM转换为GB2312
+ * @access public
+ * @param string $filename 文件名
+ * @param string $out_charset 转换后的文件编码,与iconv使用的参数一致
+ * @return void
+ */
+ public static function DetectAndSwitch($filename, $out_charset)
+ {
+ $fpr = fopen($filename, "r");
+ $char1 = fread($fpr, 1);
+ $char2 = fread($fpr, 1);
+ $char3 = fread($fpr, 1);
+
+ $originEncoding = "";
+
+ if (chr(239) == $char1 && chr(187) == $char2 && chr(191) == $char3) //UTF-8 WITH BOM
+ {
+ $originEncoding = "UTF-8 WITH BOM";
+ } elseif (chr(255) == $char1 && chr(254) == $char2) //UNICODE LE
+ {
+ self::error("不支持从UNICODE LE转换到UTF-8或GB编码");
+ fclose($fpr);
+ return;
+ } elseif (chr(254) == $char1 && chr(255) == $char2) {
+//UNICODE BE
+ self::error("不支持从UNICODE BE转换到UTF-8或GB编码");
+ fclose($fpr);
+ return;
+ } else {
+//没有文件头,可能是GB或UTF-8
+ if (rewind($fpr) === false) { //回到文件开始部分,准备逐字节读取判断编码
+ self::error($filename . "文件指针后移失败");
+ fclose($fpr);
+ return;
+ }
+
+ while (!feof($fpr)) {
+ $char = fread($fpr, 1);
+ //对于英文,GB和UTF-8都是单字节的ASCII码小于128的值
+ if (ord($char) < 128) {
+ continue;
+ }
+
+ //对于汉字GB编码第一个字节是110*****第二个字节是10******(有特例,比如联字)
+ //UTF-8编码第一个字节是1110****第二个字节是10******第三个字节是10******
+ //按位与出来结果要跟上面非星号相同,所以应该先判断UTF-8
+ //因为使用GB的掩码按位与,UTF-8的111得出来的也是110,所以要先判断UTF-8
+ if ((ord($char) & 224) == 224) {
+ //第一个字节判断通过
+ $char = fread($fpr, 1);
+ if ((ord($char) & 128) == 128) {
+ //第二个字节判断通过
+ $char = fread($fpr, 1);
+ if ((ord($char) & 128) == 128) {
+ $originEncoding = "UTF-8";
+ break;
+ }
+ }
+ }
+ if ((ord($char) & 192) == 192) {
+ //第一个字节判断通过
+ $char = fread($fpr, 1);
+ if ((ord($char) & 128) == 128) {
+ //第二个字节判断通过
+ $originEncoding = "GB2312";
+ break;
+ }
+ }
+ }
+ }
+
+ if (strtoupper($out_charset) == $originEncoding) {
+ self::info("文件" . $filename . "转码检查完成,原始文件编码" . $originEncoding);
+ fclose($fpr);
+ } else {
+ //文件需要转码
+ $originContent = "";
+
+ if ("UTF-8 WITH BOM" == $originEncoding) {
+ //跳过三个字节,把后面的内容复制一遍得到utf-8的内容
+ fseek($fpr, 3);
+ $originContent = fread($fpr, filesize($filename) - 3);
+ fclose($fpr);
+ } elseif (rewind($fpr) != false) {
+//不管是UTF-8还是GB2312,回到文件开始部分,读取内容
+ $originContent = fread($fpr, filesize($filename));
+ fclose($fpr);
+ } else {
+ self::error("文件编码不正确或指针后移失败");
+ fclose($fpr);
+ return;
+ }
+
+ //转码并保存文件
+ $content = iconv(str_replace(" WITH BOM", "", $originEncoding), strtoupper($out_charset), $originContent);
+ $fpw = fopen($filename, "w");
+ fwrite($fpw, $content);
+ fclose($fpw);
+
+ if ("" != $originEncoding) {
+ self::info("对文件" . $filename . "转码完成,原始文件编码" . $originEncoding . ",转换后文件编码" . strtoupper($out_charset));
+ } elseif ("" == $originEncoding) {
+ self::info("文件" . $filename . "中没有出现中文,但是可以断定不是带BOM的UTF-8编码,没有进行编码转换,不影响使用");
+ }
+
+ }
+ }
+
+ /**
+ * 目录遍历函数
+ * @access public
+ * @param string $path 要遍历的目录名
+ * @param string $mode 遍历模式,一般取FILES,这样只返回带路径的文件名
+ * @param array $file_types 文件后缀过滤数组
+ * @param int $maxdepth 遍历深度,-1表示遍历到最底层
+ * @return void
+ */
+ public static function searchdir($path, $mode = "FULL", $file_types = array(".html", ".php"), $maxdepth = -1, $d = 0)
+ {
+ if (substr($path, strlen($path) - 1) != '/') {
+ $path .= '/';
+ }
+
+ $dirlist = array();
+ if ("FILES" != $mode) {
+ $dirlist[] = $path;
+ }
+
+ if ($handle = @opendir($path)) {
+ while (false !== ($file = readdir($handle))) {
+ if ('.' != $file && '..' != $file) {
+ $file = $path . $file;
+ if (!is_dir($file)) {
+ if ("DIRS" != $mode) {
+ $extension = "";
+ $extpos = strrpos($file, '.');
+ if (false !== $extpos) {
+ $extension = substr($file, $extpos, strlen($file) - $extpos);
+ }
+
+ $extension = strtolower($extension);
+ if (in_array($extension, $file_types)) {
+ $dirlist[] = $file;
+ }
+
+ }
+ } elseif ($d >= 0 && ($d < $maxdepth || $maxdepth < 0)) {
+ $result = self::searchdir($file . '/', $mode, $file_types, $maxdepth, $d + 1);
+ $dirlist = array_merge($dirlist, $result);
+ }
+ }
+ }
+ closedir($handle);
+ }
+ if (0 == $d) {
+ natcasesort($dirlist);
+ }
+
+ return ($dirlist);
+ }
+
+ /**
+ * 对整个项目目录中的PHP和HTML文件行进编码转换
+ * @access public
+ * @param string $app 要遍历的项目路径
+ * @param string $mode 遍历模式,一般取FILES,这样只返回带路径的文件名
+ * @param array $file_types 文件后缀过滤数组
+ * @return void
+ */
+ public static function CodingSwitch($app = "./", $charset = 'UTF-8', $mode = "FILES", $file_types = array(".html", ".php"))
+ {
+ self::info("注意: 程序使用的文件编码检测算法可能对某些特殊字符不适用");
+ $filearr = self::searchdir($app, $mode, $file_types);
+ foreach ($filearr as $file) {
+ self::DetectAndSwitch($file, $charset);
+ }
+
+ }
+
+ public static function getError()
+ {
+ return self::$error;
+ }
+
+ public static function getInfo()
+ {
+ return self::$info;
+ }
+}
diff --git a/Framework/Library/Org/Util/Date.class.php b/Framework/Library/Org/Util/Date.class.php
new file mode 100644
index 00000000..3e1d099b
--- /dev/null
+++ b/Framework/Library/Org/Util/Date.class.php
@@ -0,0 +1,594 @@
+
+// +----------------------------------------------------------------------
+
+namespace Org\Util;
+
+/**
+ * 日期时间操作类
+ * @category ORG
+ * @package ORG
+ * @subpackage Date
+ * @author liu21st
+ * @version $Id: Date.class.php 2662 2012-01-26 06:32:50Z liu21st $
+ */
+use Org\Util\Date as Date;
+class Date
+{
+
+ /**
+ * 日期的时间戳
+ * @var integer
+ * @access protected
+ */
+ protected $date;
+
+ /**
+ * 时区
+ * @var integer
+ * @access protected
+ */
+ protected $timezone;
+
+ /**
+ * 年
+ * @var integer
+ * @access protected
+ */
+ protected $year;
+
+ /**
+ * 月
+ * @var integer
+ * @access protected
+ */
+ protected $month;
+
+ /**
+ * 日
+ * @var integer
+ * @access protected
+ */
+ protected $day;
+
+ /**
+ * 时
+ * @var integer
+ * @access protected
+ */
+ protected $hour;
+
+ /**
+ * 分
+ * @var integer
+ * @access protected
+ */
+ protected $minute;
+
+ /**
+ * 秒
+ * @var integer
+ * @access protected
+ */
+ protected $second;
+
+ /**
+ * 星期的数字表示
+ * @var integer
+ * @access protected
+ */
+ protected $weekday;
+
+ /**
+ * 星期的完整表示
+ * @var string
+ * @access protected
+ */
+ protected $cWeekday;
+
+ /**
+ * 一年中的天数 0-365
+ * @var integer
+ * @access protected
+ */
+ protected $yDay;
+
+ /**
+ * 月份的完整表示
+ * @var string
+ * @access protected
+ */
+ protected $cMonth;
+
+ /**
+ * 日期CDATE表示
+ * @var string
+ * @access protected
+ */
+ protected $CDATE;
+
+ /**
+ * 日期的YMD表示
+ * @var string
+ * @access protected
+ */
+ protected $YMD;
+
+ /**
+ * 时间的输出表示
+ * @var string
+ * @access protected
+ */
+ protected $CTIME;
+
+ // 星期的输出
+ protected $Week = array("日", "一", "二", "三", "四", "五", "六");
+
+ /**
+ * 架构函数
+ * 创建一个Date对象
+ * @param mixed $date 日期
+ * @static
+ * @access public
+ */
+ public function __construct($date = '')
+ {
+ //分析日期
+ $this->date = $this->parse($date);
+ $this->setDate($this->date);
+ }
+
+ /**
+ * 日期分析
+ * 返回时间戳
+ * @static
+ * @access public
+ * @param mixed $date 日期
+ * @return string
+ */
+ public function parse($date)
+ {
+ if (is_string($date)) {
+ if (("" == $date) || strtotime($date) == -1) {
+ //为空默认取得当前时间戳
+ $tmpdate = time();
+ } else {
+ //把字符串转换成UNIX时间戳
+ $tmpdate = strtotime($date);
+ }
+ } elseif (is_null($date)) {
+ //为空默认取得当前时间戳
+ $tmpdate = time();
+
+ } elseif (is_numeric($date)) {
+ //数字格式直接转换为时间戳
+ $tmpdate = $date;
+
+ } else {
+ if (get_class($date) == "Date") {
+ //如果是Date对象
+ $tmpdate = $date->date;
+ } else {
+ //默认取当前时间戳
+ $tmpdate = time();
+ }
+ }
+ return $tmpdate;
+ }
+
+ /**
+ * 验证日期数据是否有效
+ * @access public
+ * @param mixed $date 日期数据
+ * @return string
+ */
+ public function valid($date)
+ {
+
+ }
+
+ /**
+ * 日期参数设置
+ * @static
+ * @access public
+ * @param integer $date 日期时间戳
+ * @return void
+ */
+ public function setDate($date)
+ {
+ $dateArray = getdate($date);
+ $this->date = $dateArray[0]; //时间戳
+ $this->second = $dateArray["seconds"]; //秒
+ $this->minute = $dateArray["minutes"]; //分
+ $this->hour = $dateArray["hours"]; //时
+ $this->day = $dateArray["mday"]; //日
+ $this->month = $dateArray["mon"]; //月
+ $this->year = $dateArray["year"]; //年
+
+ $this->weekday = $dateArray["wday"]; //星期 0~6
+ $this->cWeekday = '星期' . $this->Week[$this->weekday]; //$dateArray["weekday"]; //星期完整表示
+ $this->yDay = $dateArray["yday"]; //一年中的天数 0-365
+ $this->cMonth = $dateArray["month"]; //月份的完整表示
+
+ $this->CDATE = $this->format("%Y-%m-%d"); //日期表示
+ $this->YMD = $this->format("%Y%m%d"); //简单日期
+ $this->CTIME = $this->format("%H:%M:%S"); //时间表示
+
+ return;
+ }
+
+ /**
+ * 日期格式化
+ * 默认返回 1970-01-01 11:30:45 格式
+ * @access public
+ * @param string $format 格式化参数
+ * @return string
+ */
+ public function format($format = "%Y-%m-%d %H:%M:%S")
+ {
+ return strftime($format, $this->date);
+ }
+
+ /**
+ * 是否为闰年
+ * @static
+ * @access public
+ * @return string
+ */
+ public function isLeapYear($year = '')
+ {
+ if (empty($year)) {
+ $year = $this->year;
+ }
+ return ((($year % 4) == 0) && (($year % 100) != 0) || (($year % 400) == 0));
+ }
+
+ /**
+ * 计算日期差
+ *
+ * w - weeks
+ * d - days
+ * h - hours
+ * m - minutes
+ * s - seconds
+ * @static
+ * @access public
+ * @param mixed $date 要比较的日期
+ * @param string $elaps 比较跨度
+ * @return integer
+ */
+ public function dateDiff($date, $elaps = "d")
+ {
+ $__DAYS_PER_WEEK__ = (7);
+ $__DAYS_PER_MONTH__ = (30);
+ $__DAYS_PER_YEAR__ = (365);
+ $__HOURS_IN_A_DAY__ = (24);
+ $__MINUTES_IN_A_DAY__ = (1440);
+ $__SECONDS_IN_A_DAY__ = (86400);
+ //计算天数差
+ $__DAYSELAPS = ($this->parse($date) - $this->date) / $__SECONDS_IN_A_DAY__;
+ switch ($elaps) {
+ case "y": //转换成年
+ $__DAYSELAPS = $__DAYSELAPS / $__DAYS_PER_YEAR__;
+ break;
+ case "M": //转换成月
+ $__DAYSELAPS = $__DAYSELAPS / $__DAYS_PER_MONTH__;
+ break;
+ case "w": //转换成星期
+ $__DAYSELAPS = $__DAYSELAPS / $__DAYS_PER_WEEK__;
+ break;
+ case "h": //转换成小时
+ $__DAYSELAPS = $__DAYSELAPS * $__HOURS_IN_A_DAY__;
+ break;
+ case "m": //转换成分钟
+ $__DAYSELAPS = $__DAYSELAPS * $__MINUTES_IN_A_DAY__;
+ break;
+ case "s": //转换成秒
+ $__DAYSELAPS = $__DAYSELAPS * $__SECONDS_IN_A_DAY__;
+ break;
+ }
+ return $__DAYSELAPS;
+ }
+
+ /**
+ * 人性化的计算日期差
+ * @static
+ * @access public
+ * @param mixed $time 要比较的时间
+ * @param mixed $precision 返回的精度
+ * @return string
+ */
+ public function timeDiff($time, $precision = false)
+ {
+ if (!is_numeric($precision) && !is_bool($precision)) {
+ static $_diff = array('y' => '年', 'M' => '个月', 'd' => '天', 'w' => '周', 's' => '秒', 'h' => '小时', 'm' => '分钟');
+ return ceil($this->dateDiff($time, $precision)) . $_diff[$precision] . '前';
+ }
+ $diff = abs($this->parse($time) - $this->date);
+ static $chunks = array(array(31536000, '年'), array(2592000, '个月'), array(604800, '周'), array(86400, '天'), array(3600, '小时'), array(60, '分钟'), array(1, '秒'));
+ $count = 0;
+ $since = '';
+ for ($i = 0; $i < count($chunks); $i++) {
+ if ($diff >= $chunks[$i][0]) {
+ $num = floor($diff / $chunks[$i][0]);
+ $since .= sprintf('%d' . $chunks[$i][1], $num);
+ $diff = (int) ($diff - $chunks[$i][0] * $num);
+ $count++;
+ if (!$precision || $count >= $precision) {
+ break;
+ }
+ }
+ }
+ return $since . '前';
+ }
+
+ /**
+ * 返回周的某一天 返回Date对象
+ * @access public
+ * @return Date
+ */
+ public function getDayOfWeek($n)
+ {
+ $week = array(0 => 'sunday', 1 => 'monday', 2 => 'tuesday', 3 => 'wednesday', 4 => 'thursday', 5 => 'friday', 6 => 'saturday');
+ return (new Date($week[$n]));
+ }
+
+ /**
+ * 计算周的第一天 返回Date对象
+ * @access public
+ * @return Date
+ */
+ public function firstDayOfWeek()
+ {
+ return $this->getDayOfWeek(1);
+ }
+
+ /**
+ * 计算月份的第一天 返回Date对象
+ * @access public
+ * @return Date
+ */
+ public function firstDayOfMonth()
+ {
+ return (new Date(mktime(0, 0, 0, $this->month, 1, $this->year)));
+ }
+
+ /**
+ * 计算年份的第一天 返回Date对象
+ * @access public
+ * @return Date
+ */
+ public function firstDayOfYear()
+ {
+ return (new Date(mktime(0, 0, 0, 1, 1, $this->year)));
+ }
+
+ /**
+ * 计算周的最后一天 返回Date对象
+ * @access public
+ * @return Date
+ */
+ public function lastDayOfWeek()
+ {
+ return $this->getDayOfWeek(0);
+ }
+
+ /**
+ * 计算月份的最后一天 返回Date对象
+ * @access public
+ * @return Date
+ */
+ public function lastDayOfMonth()
+ {
+ return (new Date(mktime(0, 0, 0, $this->month + 1, 0, $this->year)));
+ }
+
+ /**
+ * 计算年份的最后一天 返回Date对象
+ * @access public
+ * @return Date
+ */
+ public function lastDayOfYear()
+ {
+ return (new Date(mktime(0, 0, 0, 1, 0, $this->year + 1)));
+ }
+
+ /**
+ * 计算月份的最大天数
+ * @access public
+ * @return integer
+ */
+ public function maxDayOfMonth()
+ {
+ $result = $this->dateDiff(strtotime($this->dateAdd(1, 'm')), 'd');
+ return $result;
+ }
+
+ /**
+ * 取得指定间隔日期
+ *
+ * yyyy - 年
+ * q - 季度
+ * m - 月
+ * y - day of year
+ * d - 日
+ * w - 周
+ * ww - week of year
+ * h - 小时
+ * n - 分钟
+ * s - 秒
+ * @access public
+ * @param integer $number 间隔数目
+ * @param string $interval 比较类型
+ * @return Date
+ */
+ public function dateAdd($number = 0, $interval = "d")
+ {
+ $hours = $this->hour;
+ $minutes = $this->minute;
+ $seconds = $this->second;
+ $month = $this->month;
+ $day = $this->day;
+ $year = $this->year;
+
+ switch ($interval) {
+ case "yyyy":
+ //---Add $number to year
+ $year += $number;
+ break;
+
+ case "q":
+ //---Add $number to quarter
+ $month += ($number * 3);
+ break;
+
+ case "m":
+ //---Add $number to month
+ $month += $number;
+ break;
+
+ case "y":
+ case "d":
+ case "w":
+ //---Add $number to day of year, day, day of week
+ $day += $number;
+ break;
+
+ case "ww":
+ //---Add $number to week
+ $day += ($number * 7);
+ break;
+
+ case "h":
+ //---Add $number to hours
+ $hours += $number;
+ break;
+
+ case "n":
+ //---Add $number to minutes
+ $minutes += $number;
+ break;
+
+ case "s":
+ //---Add $number to seconds
+ $seconds += $number;
+ break;
+ }
+
+ return (new Date(mktime($hours,
+ $minutes,
+ $seconds,
+ $month,
+ $day,
+ $year)));
+
+ }
+
+ /**
+ * 日期数字转中文
+ * 用于日和月、周
+ * @static
+ * @access public
+ * @param integer $number 日期数字
+ * @return string
+ */
+ public function numberToCh($number)
+ {
+ $number = intval($number);
+ $array = array('一', '二', '三', '四', '五', '六', '七', '八', '九', '十');
+ $str = '';
+ if (0 == $number) {$str .= "十";}
+ if ($number < 10) {
+ $str .= $array[$number - 1];
+ } elseif ($number < 20) {
+ $str .= "十" . $array[$number - 11];
+ } elseif ($number < 30) {
+ $str .= "二十" . $array[$number - 21];
+ } else {
+ $str .= "三十" . $array[$number - 31];
+ }
+ return $str;
+ }
+
+ /**
+ * 年份数字转中文
+ * @static
+ * @access public
+ * @param integer $yearStr 年份数字
+ * @param boolean $flag 是否显示公元
+ * @return string
+ */
+ public function yearToCh($yearStr, $flag = false)
+ {
+ $array = array('零', '一', '二', '三', '四', '五', '六', '七', '八', '九');
+ $str = $flag ? '公元' : '';
+ for ($i = 0; $i < 4; $i++) {
+ $str .= $array[substr($yearStr, $i, 1)];
+ }
+ return $str;
+ }
+
+ /**
+ * 判断日期 所属 干支 生肖 星座
+ * type 参数:XZ 星座 GZ 干支 SX 生肖
+ *
+ * @static
+ * @access public
+ * @param string $type 获取信息类型
+ * @return string
+ */
+ public function magicInfo($type)
+ {
+ $result = '';
+ $m = $this->month;
+ $y = $this->year;
+ $d = $this->day;
+
+ switch ($type) {
+ case 'XZ': //星座
+ $XZDict = array('摩羯', '宝瓶', '双鱼', '白羊', '金牛', '双子', '巨蟹', '狮子', '处女', '天秤', '天蝎', '射手');
+ $Zone = array(1222, 122, 222, 321, 421, 522, 622, 722, 822, 922, 1022, 1122, 1222);
+ if ((100 * $m + $d) >= $Zone[0] || (100 * $m + $d) < $Zone[1]) {
+ $i = 0;
+ } else {
+ for ($i = 1; $i < 12; $i++) {
+ if ((100 * $m + $d) >= $Zone[$i] && (100 * $m + $d) < $Zone[$i + 1]) {
+ break;
+ }
+
+ }
+ }
+
+ $result = $XZDict[$i] . '座';
+ break;
+
+ case 'GZ': //干支
+ $GZDict = array(
+ array('甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'),
+ array('子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'),
+ );
+ $i = $y - 1900 + 36;
+ $result = $GZDict[0][$i % 10] . $GZDict[1][$i % 12];
+ break;
+
+ case 'SX': //生肖
+ $SXDict = array('鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪');
+ $result = $SXDict[($y - 4) % 12];
+ break;
+
+ }
+ return $result;
+ }
+
+ public function __toString()
+ {
+ return $this->format();
+ }
+}
diff --git a/Framework/Library/Org/Util/Rbac.class.php b/Framework/Library/Org/Util/Rbac.class.php
new file mode 100644
index 00000000..51bd8f79
--- /dev/null
+++ b/Framework/Library/Org/Util/Rbac.class.php
@@ -0,0 +1,306 @@
+
+// +----------------------------------------------------------------------
+namespace Org\Util;
+
+use Think\Db;
+
+/**
+ * 基于角色的数据库方式验证类
+ */
+// 配置文件增加设置
+// USER_AUTH_ON 是否需要认证
+// USER_AUTH_TYPE 认证类型
+// USER_AUTH_KEY 认证识别号
+// REQUIRE_AUTH_MODULE 需要认证模块
+// NOT_AUTH_MODULE 无需认证模块
+// USER_AUTH_GATEWAY 认证网关
+// RBAC_DB_DSN 数据库连接DSN
+// RBAC_ROLE_TABLE 角色表名称
+// RBAC_USER_TABLE 用户表名称
+// RBAC_ACCESS_TABLE 权限表名称
+// RBAC_NODE_TABLE 节点表名称
+/*
+-- --------------------------------------------------------
+CREATE TABLE IF NOT EXISTS `think_access` (
+`role_id` smallint(6) unsigned NOT NULL,
+`node_id` smallint(6) unsigned NOT NULL,
+`level` tinyint(1) NOT NULL,
+`module` varchar(50) DEFAULT NULL,
+KEY `groupId` (`role_id`),
+KEY `nodeId` (`node_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS `think_node` (
+`id` smallint(6) unsigned NOT NULL AUTO_INCREMENT,
+`name` varchar(20) NOT NULL,
+`title` varchar(50) DEFAULT NULL,
+`status` tinyint(1) DEFAULT '0',
+`remark` varchar(255) DEFAULT NULL,
+`sort` smallint(6) unsigned DEFAULT NULL,
+`pid` smallint(6) unsigned NOT NULL,
+`level` tinyint(1) unsigned NOT NULL,
+PRIMARY KEY (`id`),
+KEY `level` (`level`),
+KEY `pid` (`pid`),
+KEY `status` (`status`),
+KEY `name` (`name`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS `think_role` (
+`id` smallint(6) unsigned NOT NULL AUTO_INCREMENT,
+`name` varchar(20) NOT NULL,
+`pid` smallint(6) DEFAULT NULL,
+`status` tinyint(1) unsigned DEFAULT NULL,
+`remark` varchar(255) DEFAULT NULL,
+PRIMARY KEY (`id`),
+KEY `pid` (`pid`),
+KEY `status` (`status`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 ;
+
+CREATE TABLE IF NOT EXISTS `think_role_user` (
+`role_id` mediumint(9) unsigned DEFAULT NULL,
+`user_id` char(32) DEFAULT NULL,
+KEY `group_id` (`role_id`),
+KEY `user_id` (`user_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+ */
+class Rbac
+{
+ // 认证方法
+ public static function authenticate($map, $model = '')
+ {
+ if (empty($model)) {
+ $model = C('USER_AUTH_MODEL');
+ }
+
+ //使用给定的Map进行认证
+ return M($model)->where($map)->find();
+ }
+
+ //用于检测用户权限的方法,并保存到Session中
+ public static function saveAccessList($authId = null)
+ {
+ if (null === $authId) {
+ $authId = $_SESSION[C('USER_AUTH_KEY')];
+ }
+
+ // 如果使用普通权限模式,保存当前用户的访问权限列表
+ // 对管理员开发所有权限
+ if (C('USER_AUTH_TYPE') != 2 && !$_SESSION[C('ADMIN_AUTH_KEY')]) {
+ $_SESSION['_ACCESS_LIST'] = self::getAccessList($authId);
+ }
+
+ return;
+ }
+
+ // 取得模块的所属记录访问权限列表 返回有权限的记录ID数组
+ public static function getRecordAccessList($authId = null, $module = '')
+ {
+ if (null === $authId) {
+ $authId = $_SESSION[C('USER_AUTH_KEY')];
+ }
+
+ if (empty($module)) {
+ $module = CONTROLLER_NAME;
+ }
+
+ //获取权限访问列表
+ $accessList = self::getModuleAccessList($authId, $module);
+ return $accessList;
+ }
+
+ //检查当前操作是否需要认证
+ public static function checkAccess()
+ {
+ //如果项目要求认证,并且当前模块需要认证,则进行权限认证
+ if (C('USER_AUTH_ON')) {
+ $_module = array();
+ $_action = array();
+ if ("" != C('REQUIRE_AUTH_MODULE')) {
+ //需要认证的模块
+ $_module['yes'] = explode(',', strtoupper(C('REQUIRE_AUTH_MODULE')));
+ } else {
+ //无需认证的模块
+ $_module['no'] = explode(',', strtoupper(C('NOT_AUTH_MODULE')));
+ }
+ //检查当前模块是否需要认证
+ if ((!empty($_module['no']) && !in_array(strtoupper(CONTROLLER_NAME), $_module['no'])) || (!empty($_module['yes']) && in_array(strtoupper(CONTROLLER_NAME), $_module['yes']))) {
+ if ("" != C('REQUIRE_AUTH_ACTION')) {
+ //需要认证的操作
+ $_action['yes'] = explode(',', strtoupper(C('REQUIRE_AUTH_ACTION')));
+ } else {
+ //无需认证的操作
+ $_action['no'] = explode(',', strtoupper(C('NOT_AUTH_ACTION')));
+ }
+ //检查当前操作是否需要认证
+ if ((!empty($_action['no']) && !in_array(strtoupper(ACTION_NAME), $_action['no'])) || (!empty($_action['yes']) && in_array(strtoupper(ACTION_NAME), $_action['yes']))) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ // 登录检查
+ public static function checkLogin()
+ {
+ //检查当前操作是否需要认证
+ if (self::checkAccess()) {
+ //检查认证识别号
+ if (!$_SESSION[C('USER_AUTH_KEY')]) {
+ if (C('GUEST_AUTH_ON')) {
+ // 开启游客授权访问
+ if (!isset($_SESSION['_ACCESS_LIST']))
+ // 保存游客权限
+ {
+ self::saveAccessList(C('GUEST_AUTH_ID'));
+ }
+
+ } else {
+ // 禁止游客访问跳转到认证网关
+ redirect(PHP_FILE . C('USER_AUTH_GATEWAY'));
+ }
+ }
+ }
+ return true;
+ }
+
+ //权限认证的过滤器方法
+ public static function AccessDecision($appName = MODULE_NAME)
+ {
+ //检查是否需要认证
+ if (self::checkAccess()) {
+ //存在认证识别号,则进行进一步的访问决策
+ $accessGuid = md5($appName . CONTROLLER_NAME . ACTION_NAME);
+ if (empty($_SESSION[C('ADMIN_AUTH_KEY')])) {
+ if (C('USER_AUTH_TYPE') == 2) {
+ //加强验证和即时验证模式 更加安全 后台权限修改可以即时生效
+ //通过数据库进行访问检查
+ $accessList = self::getAccessList($_SESSION[C('USER_AUTH_KEY')]);
+ } else {
+ // 如果是管理员或者当前操作已经认证过,无需再次认证
+ if ($_SESSION[$accessGuid]) {
+ return true;
+ }
+ //登录验证模式,比较登录后保存的权限访问列表
+ $accessList = $_SESSION['_ACCESS_LIST'];
+ }
+ //判断是否为组件化模式,如果是,验证其全模块名
+ if (!isset($accessList[strtoupper($appName)][strtoupper(CONTROLLER_NAME)][strtoupper(ACTION_NAME)])) {
+ $_SESSION[$accessGuid] = false;
+ return false;
+ } else {
+ $_SESSION[$accessGuid] = true;
+ }
+ } else {
+ //管理员无需认证
+ return true;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 取得当前认证号的所有权限列表
+ * @param integer $authId 用户ID
+ * @access public
+ */
+ public static function getAccessList($authId)
+ {
+ // Db方式权限数据
+ $db = Db::getInstance(C('RBAC_DB_DSN'));
+ $table = array('role' => C('RBAC_ROLE_TABLE'), 'user' => C('RBAC_USER_TABLE'), 'access' => C('RBAC_ACCESS_TABLE'), 'node' => C('RBAC_NODE_TABLE'));
+ $sql = "select node.id,node.name from " .
+ $table['role'] . " as role," .
+ $table['user'] . " as user," .
+ $table['access'] . " as access ," .
+ $table['node'] . " as node " .
+ "where user.user_id='{$authId}' and user.role_id=role.id and ( access.role_id=role.id or (access.role_id=role.pid and role.pid!=0 ) ) and role.status=1 and access.node_id=node.id and node.level=1 and node.status=1";
+ $apps = $db->query($sql);
+ $access = array();
+ foreach ($apps as $key => $app) {
+ $appId = $app['id'];
+ $appName = $app['name'];
+ // 读取项目的模块权限
+ $access[strtoupper($appName)] = array();
+ $sql = "select node.id,node.name from " .
+ $table['role'] . " as role," .
+ $table['user'] . " as user," .
+ $table['access'] . " as access ," .
+ $table['node'] . " as node " .
+ "where user.user_id='{$authId}' and user.role_id=role.id and ( access.role_id=role.id or (access.role_id=role.pid and role.pid!=0 ) ) and role.status=1 and access.node_id=node.id and node.level=2 and node.pid={$appId} and node.status=1";
+ $modules = $db->query($sql);
+ // 判断是否存在公共模块的权限
+ $publicAction = array();
+ foreach ($modules as $key => $module) {
+ $moduleId = $module['id'];
+ $moduleName = $module['name'];
+ if ('PUBLIC' == strtoupper($moduleName)) {
+ $sql = "select node.id,node.name from " .
+ $table['role'] . " as role," .
+ $table['user'] . " as user," .
+ $table['access'] . " as access ," .
+ $table['node'] . " as node " .
+ "where user.user_id='{$authId}' and user.role_id=role.id and ( access.role_id=role.id or (access.role_id=role.pid and role.pid!=0 ) ) and role.status=1 and access.node_id=node.id and node.level=3 and node.pid={$moduleId} and node.status=1";
+ $rs = $db->query($sql);
+ foreach ($rs as $a) {
+ $publicAction[$a['name']] = $a['id'];
+ }
+ unset($modules[$key]);
+ break;
+ }
+ }
+ // 依次读取模块的操作权限
+ foreach ($modules as $key => $module) {
+ $moduleId = $module['id'];
+ $moduleName = $module['name'];
+ $sql = "select node.id,node.name from " .
+ $table['role'] . " as role," .
+ $table['user'] . " as user," .
+ $table['access'] . " as access ," .
+ $table['node'] . " as node " .
+ "where user.user_id='{$authId}' and user.role_id=role.id and ( access.role_id=role.id or (access.role_id=role.pid and role.pid!=0 ) ) and role.status=1 and access.node_id=node.id and node.level=3 and node.pid={$moduleId} and node.status=1";
+ $rs = $db->query($sql);
+ $action = array();
+ foreach ($rs as $a) {
+ $action[$a['name']] = $a['id'];
+ }
+ // 和公共模块的操作权限合并
+ $action += $publicAction;
+ $access[strtoupper($appName)][strtoupper($moduleName)] = array_change_key_case($action, CASE_UPPER);
+ }
+ }
+ return $access;
+ }
+
+ // 读取模块所属的记录访问权限
+ public static function getModuleAccessList($authId, $module)
+ {
+ // Db方式
+ $db = Db::getInstance(C('RBAC_DB_DSN'));
+ $table = array('role' => C('RBAC_ROLE_TABLE'), 'user' => C('RBAC_USER_TABLE'), 'access' => C('RBAC_ACCESS_TABLE'));
+ $sql = "select access.node_id from " .
+ $table['role'] . " as role," .
+ $table['user'] . " as user," .
+ $table['access'] . " as access " .
+ "where user.user_id='{$authId}' and user.role_id=role.id and ( access.role_id=role.id or (access.role_id=role.pid and role.pid!=0 ) ) and role.status=1 and access.module='{$module}' and access.status=1";
+ $rs = $db->query($sql);
+ $access = array();
+ foreach ($rs as $node) {
+ $access[] = $node['node_id'];
+ }
+ return $access;
+ }
+}
diff --git a/Framework/Library/Org/Util/Stack.class.php b/Framework/Library/Org/Util/Stack.class.php
new file mode 100644
index 00000000..2a1412ca
--- /dev/null
+++ b/Framework/Library/Org/Util/Stack.class.php
@@ -0,0 +1,55 @@
+
+// +----------------------------------------------------------------------
+namespace Org\Util;
+
+/**
+ * Stack实现类
+ * @category ORG
+ * @package ORG
+ * @subpackage Util
+ * @author liu21st
+ */
+class Stack extends ArrayList
+{
+
+ /**
+ * 架构函数
+ * @access public
+ * @param array $values 初始化数组元素
+ */
+ public function __construct($values = array())
+ {
+ parent::__construct($values);
+ }
+
+ /**
+ * 将堆栈的内部指针指向第一个单元
+ * @access public
+ * @return mixed
+ */
+ public function peek()
+ {
+ return reset($this->toArray());
+ }
+
+ /**
+ * 元素进栈
+ * @access public
+ * @param mixed $value
+ * @return mixed
+ */
+ public function push($value)
+ {
+ $this->add($value);
+ return $value;
+ }
+
+}
diff --git a/Framework/Library/Org/Util/String.class.php b/Framework/Library/Org/Util/String.class.php
new file mode 100644
index 00000000..87b97a2c
--- /dev/null
+++ b/Framework/Library/Org/Util/String.class.php
@@ -0,0 +1,277 @@
+
+// +----------------------------------------------------------------------
+namespace Org\Util;
+
+class String
+{
+
+ /**
+ * 生成UUID 单机使用
+ * @access public
+ * @return string
+ */
+ public static function uuid()
+ {
+ $charid = md5(uniqid(mt_rand(), true));
+ $hyphen = chr(45); // "-"
+ $uuid = chr(123) // "{"
+ . substr($charid, 0, 8) . $hyphen
+ . substr($charid, 8, 4) . $hyphen
+ . substr($charid, 12, 4) . $hyphen
+ . substr($charid, 16, 4) . $hyphen
+ . substr($charid, 20, 12)
+ . chr(125); // "}"
+ return $uuid;
+ }
+
+ /**
+ * 生成Guid主键
+ * @return Boolean
+ */
+ public static function keyGen()
+ {
+ return str_replace('-', '', substr(String::uuid(), 1, -1));
+ }
+
+ /**
+ * 检查字符串是否是UTF8编码
+ * @param string $string 字符串
+ * @return Boolean
+ */
+ public static function isUtf8($str)
+ {
+ $c = 0;
+ $b = 0;
+ $bits = 0;
+ $len = strlen($str);
+ for ($i = 0; $i < $len; $i++) {
+ $c = ord($str[$i]);
+ if ($c > 128) {
+ if (($c >= 254)) {
+ return false;
+ } elseif ($c >= 252) {
+ $bits = 6;
+ } elseif ($c >= 248) {
+ $bits = 5;
+ } elseif ($c >= 240) {
+ $bits = 4;
+ } elseif ($c >= 224) {
+ $bits = 3;
+ } elseif ($c >= 192) {
+ $bits = 2;
+ } else {
+ return false;
+ }
+
+ if (($i + $bits) > $len) {
+ return false;
+ }
+
+ while ($bits > 1) {
+ $i++;
+ $b = ord($str[$i]);
+ if ($b < 128 || $b > 191) {
+ return false;
+ }
+
+ $bits--;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 字符串截取,支持中文和其他编码
+ * @static
+ * @access public
+ * @param string $str 需要转换的字符串
+ * @param string $start 开始位置
+ * @param string $length 截取长度
+ * @param string $charset 编码格式
+ * @param string $suffix 截断显示字符
+ * @return string
+ */
+ public static function msubstr($str, $start = 0, $length, $charset = "utf-8", $suffix = true)
+ {
+ if (function_exists("mb_substr")) {
+ $slice = mb_substr($str, $start, $length, $charset);
+ } elseif (function_exists('iconv_substr')) {
+ $slice = iconv_substr($str, $start, $length, $charset);
+ } else {
+ $re['utf-8'] = "/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xff][\x80-\xbf]{3}/";
+ $re['gb2312'] = "/[\x01-\x7f]|[\xb0-\xf7][\xa0-\xfe]/";
+ $re['gbk'] = "/[\x01-\x7f]|[\x81-\xfe][\x40-\xfe]/";
+ $re['big5'] = "/[\x01-\x7f]|[\x81-\xfe]([\x40-\x7e]|\xa1-\xfe])/";
+ preg_match_all($re[$charset], $str, $match);
+ $slice = join("", array_slice($match[0], $start, $length));
+ }
+ return $suffix ? $slice . '...' : $slice;
+ }
+
+ /**
+ * 产生随机字串,可用来自动生成密码
+ * 默认长度6位 字母和数字混合 支持中文
+ * @param string $len 长度
+ * @param string $type 字串类型
+ * 0 字母 1 数字 其它 混合
+ * @param string $addChars 额外字符
+ * @return string
+ */
+ public static function randString($len = 6, $type = '', $addChars = '')
+ {
+ $str = '';
+ switch ($type) {
+ case 0:
+ $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' . $addChars;
+ break;
+ case 1:
+ $chars = str_repeat('0123456789', 3);
+ break;
+ case 2:
+ $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . $addChars;
+ break;
+ case 3:
+ $chars = 'abcdefghijklmnopqrstuvwxyz' . $addChars;
+ break;
+ case 4:
+ $chars = "们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书术状厂须离再目海交权且儿青才证低越际八试规斯近注办布门铁需走议县兵固除般引齿千胜细影济白格效置推空配刀叶率述今选养德话查差半敌始片施响收华觉备名红续均药标记难存测士身紧液派准斤角降维板许破述技消底床田势端感往神便贺村构照容非搞亚磨族火段算适讲按值美态黄易彪服早班麦削信排台声该击素张密害侯草何树肥继右属市严径螺检左页抗苏显苦英快称坏移约巴材省黑武培著河帝仅针怎植京助升王眼她抓含苗副杂普谈围食射源例致酸旧却充足短划剂宣环落首尺波承粉践府鱼随考刻靠够满夫失包住促枝局菌杆周护岩师举曲春元超负砂封换太模贫减阳扬江析亩木言球朝医校古呢稻宋听唯输滑站另卫字鼓刚写刘微略范供阿块某功套友限项余倒卷创律雨让骨远帮初皮播优占死毒圈伟季训控激找叫云互跟裂粮粒母练塞钢顶策双留误础吸阻故寸盾晚丝女散焊功株亲院冷彻弹错散商视艺灭版烈零室轻血倍缺厘泵察绝富城冲喷壤简否柱李望盘磁雄似困巩益洲脱投送奴侧润盖挥距触星松送获兴独官混纪依未突架宽冬章湿偏纹吃执阀矿寨责熟稳夺硬价努翻奇甲预职评读背协损棉侵灰虽矛厚罗泥辟告卵箱掌氧恩爱停曾溶营终纲孟钱待尽俄缩沙退陈讨奋械载胞幼哪剥迫旋征槽倒握担仍呀鲜吧卡粗介钻逐弱脚怕盐末阴丰雾冠丙街莱贝辐肠付吉渗瑞惊顿挤秒悬姆烂森糖圣凹陶词迟蚕亿矩康遵牧遭幅园腔订香肉弟屋敏恢忘编印蜂急拿扩伤飞露核缘游振操央伍域甚迅辉异序免纸夜乡久隶缸夹念兰映沟乙吗儒杀汽磷艰晶插埃燃欢铁补咱芽永瓦倾阵碳演威附牙芽永瓦斜灌欧献顺猪洋腐请透司危括脉宜笑若尾束壮暴企菜穗楚汉愈绿拖牛份染既秋遍锻玉夏疗尖殖井费州访吹荣铜沿替滚客召旱悟刺脑措贯藏敢令隙炉壳硫煤迎铸粘探临薄旬善福纵择礼愿伏残雷延烟句纯渐耕跑泽慢栽鲁赤繁境潮横掉锥希池败船假亮谓托伙哲怀割摆贡呈劲财仪沉炼麻罪祖息车穿货销齐鼠抽画饲龙库守筑房歌寒喜哥洗蚀废纳腹乎录镜妇恶脂庄擦险赞钟摇典柄辩竹谷卖乱虚桥奥伯赶垂途额壁网截野遗静谋弄挂课镇妄盛耐援扎虑键归符庆聚绕摩忙舞遇索顾胶羊湖钉仁音迹碎伸灯避泛亡答勇频皇柳哈揭甘诺概宪浓岛袭谁洪谢炮浇斑讯懂灵蛋闭孩释乳巨徒私银伊景坦累匀霉杜乐勒隔弯绩招绍胡呼痛峰零柴簧午跳居尚丁秦稍追梁折耗碱殊岗挖氏刃剧堆赫荷胸衡勤膜篇登驻案刊秧缓凸役剪川雪链渔啦脸户洛孢勃盟买杨宗焦赛旗滤硅炭股坐蒸凝竟陷枪黎救冒暗洞犯筒您宋弧爆谬涂味津臂障褐陆啊健尊豆拔莫抵桑坡缝警挑污冰柬嘴啥饭塑寄赵喊垫丹渡耳刨虎笔稀昆浪萨茶滴浅拥穴覆伦娘吨浸袖珠雌妈紫戏塔锤震岁貌洁剖牢锋疑霸闪埔猛诉刷狠忽灾闹乔唐漏闻沈熔氯荒茎男凡抢像浆旁玻亦忠唱蒙予纷捕锁尤乘乌智淡允叛畜俘摸锈扫毕璃宝芯爷鉴秘净蒋钙肩腾枯抛轨堂拌爸循诱祝励肯酒绳穷塘燥泡袋朗喂铝软渠颗惯贸粪综墙趋彼届墨碍启逆卸航衣孙龄岭骗休借" . $addChars;
+ break;
+ default:
+ // 默认去掉了容易混淆的字符oOLl和数字01,要添加请使用addChars参数
+ $chars = 'ABCDEFGHIJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789' . $addChars;
+ break;
+ }
+ if ($len > 10) {
+//位数过长重复字符串一定次数
+ $chars = 1 == $type ? str_repeat($chars, $len) : str_repeat($chars, 5);
+ }
+ if (4 != $type) {
+ $chars = str_shuffle($chars);
+ $str = substr($chars, 0, $len);
+ } else {
+ // 中文随机字
+ for ($i = 0; $i < $len; $i++) {
+ $str .= self::msubstr($chars, floor(mt_rand(0, mb_strlen($chars, 'utf-8') - 1)), 1, 'utf-8', false);
+ }
+ }
+ return $str;
+ }
+
+ /**
+ * 生成一定数量的随机数,并且不重复
+ * @param integer $number 数量
+ * @param string $len 长度
+ * @param string $type 字串类型
+ * 0 字母 1 数字 其它 混合
+ * @return string
+ */
+ public static function buildCountRand($number, $length = 4, $mode = 1)
+ {
+ if (1 == $mode && $length < strlen($number)) {
+ //不足以生成一定数量的不重复数字
+ return false;
+ }
+ $rand = array();
+ for ($i = 0; $i < $number; $i++) {
+ $rand[] = self::randString($length, $mode);
+ }
+ $unqiue = array_unique($rand);
+ if (count($unqiue) == count($rand)) {
+ return $rand;
+ }
+ $count = count($rand) - count($unqiue);
+ for ($i = 0; $i < $count * 3; $i++) {
+ $rand[] = self::randString($length, $mode);
+ }
+ $rand = array_slice(array_unique($rand), 0, $number);
+ return $rand;
+ }
+
+ /**
+ * 带格式生成随机字符 支持批量生成
+ * 但可能存在重复
+ * @param string $format 字符格式
+ * # 表示数字 * 表示字母和数字 $ 表示字母
+ * @param integer $number 生成数量
+ * @return string | array
+ */
+ public static function buildFormatRand($format, $number = 1)
+ {
+ $str = array();
+ $length = strlen($format);
+ for ($j = 0; $j < $number; $j++) {
+ $strtemp = '';
+ for ($i = 0; $i < $length; $i++) {
+ $char = substr($format, $i, 1);
+ switch ($char) {
+ case "*": //字母和数字混合
+ $strtemp .= String::randString(1);
+ break;
+ case "#": //数字
+ $strtemp .= String::randString(1, 1);
+ break;
+ case "$": //大写字母
+ $strtemp .= String::randString(1, 2);
+ break;
+ default: //其他格式均不转换
+ $strtemp .= $char;
+ break;
+ }
+ }
+ $str[] = $strtemp;
+ }
+ return 1 == $number ? $strtemp : $str;
+ }
+
+ /**
+ * 获取一定范围内的随机数字 位数不足补零
+ * @param integer $min 最小值
+ * @param integer $max 最大值
+ * @return string
+ */
+ public static function randNumber($min, $max)
+ {
+ return sprintf("%0" . strlen($max) . "d", mt_rand($min, $max));
+ }
+
+ // 自动转换字符集 支持数组转换
+ public static function autoCharset($string, $from = 'gbk', $to = 'utf-8')
+ {
+ $from = strtoupper($from) == 'UTF8' ? 'utf-8' : $from;
+ $to = strtoupper($to) == 'UTF8' ? 'utf-8' : $to;
+ if (strtoupper($from) === strtoupper($to) || empty($string) || (is_scalar($string) && !is_string($string))) {
+ //如果编码相同或者非字符串标量则不转换
+ return $string;
+ }
+ if (is_string($string)) {
+ if (function_exists('mb_convert_encoding')) {
+ return mb_convert_encoding($string, $to, $from);
+ } elseif (function_exists('iconv')) {
+ return iconv($from, $to, $string);
+ } else {
+ return $string;
+ }
+ } elseif (is_array($string)) {
+ foreach ($string as $key => $val) {
+ $_key = self::autoCharset($key, $from, $to);
+ $string[$_key] = self::autoCharset($val, $from, $to);
+ if ($key != $_key) {
+ unset($string[$key]);
+ }
+
+ }
+ return $string;
+ } else {
+ return $string;
+ }
+ }
+}
diff --git a/Framework/Library/Think/App.class.php b/Framework/Library/Think/App.class.php
new file mode 100644
index 00000000..d9a87663
--- /dev/null
+++ b/Framework/Library/Think/App.class.php
@@ -0,0 +1,223 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * ThinkPHP 应用程序类 执行应用过程管理
+ */
+
+class App
+{
+
+ /**
+ * 应用程序初始化
+ * @access public
+ * @return void
+ */
+ public static function init()
+ {
+ // 日志目录转换为绝对路径 默认情况下存储到公共模块下面
+ C('LOG_PATH', realpath(LOG_PATH) . '/Common/');
+
+ // 定义当前请求的系统常量
+ define('NOW_TIME', $_SERVER['REQUEST_TIME']);
+ define('REQUEST_METHOD', $_SERVER['REQUEST_METHOD']);
+ define('IS_GET', REQUEST_METHOD == 'GET' ? true : false);
+ define('IS_POST', REQUEST_METHOD == 'POST' ? true : false);
+ define('IS_PUT', REQUEST_METHOD == 'PUT' ? true : false);
+ define('IS_DELETE', REQUEST_METHOD == 'DELETE' ? true : false);
+
+ // URL调度
+ Dispatcher::dispatch();
+
+ if (C('REQUEST_VARS_FILTER')) {
+ // 全局安全过滤
+ array_walk_recursive($_GET, 'think_filter');
+ array_walk_recursive($_POST, 'think_filter');
+ array_walk_recursive($_REQUEST, 'think_filter');
+ }
+
+ // URL调度结束标签
+ Hook::listen('url_dispatch');
+
+ define('IS_AJAX', ((isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') || !empty($_POST[C('VAR_AJAX_SUBMIT')]) || !empty($_GET[C('VAR_AJAX_SUBMIT')])) ? true : false);
+
+ // TMPL_EXCEPTION_FILE 改为绝对地址
+ C('TMPL_EXCEPTION_FILE', realpath(C('TMPL_EXCEPTION_FILE')));
+ return;
+ }
+
+ /**
+ * 执行应用程序
+ * @access public
+ * @return void
+ */
+ public static function exec()
+ {
+
+ if (!preg_match('/^[A-Za-z](\/|\w)*$/', CONTROLLER_NAME)) {
+ // 安全检测
+ $module = false;
+ } elseif (C('ACTION_BIND_CLASS')) {
+ // 操作绑定到类:模块\Controller\控制器\操作
+ $layer = C('DEFAULT_C_LAYER');
+ if (is_dir(MODULE_PATH . $layer . '/' . CONTROLLER_NAME)) {
+ $namespace = MODULE_NAME . '\\' . $layer . '\\' . CONTROLLER_NAME . '\\';
+ } else {
+ // 空控制器
+ $namespace = MODULE_NAME . '\\' . $layer . '\\_empty\\';
+ }
+ $actionName = strtolower(ACTION_NAME);
+ if (class_exists($namespace . $actionName)) {
+ $class = $namespace . $actionName;
+ } elseif (class_exists($namespace . '_empty')) {
+ // 空操作
+ $class = $namespace . '_empty';
+ } else {
+ E(L('_ERROR_ACTION_') . ':' . ACTION_NAME);
+ }
+ $module = new $class;
+ // 操作绑定到类后 固定执行run入口
+ $action = 'run';
+ } else {
+ //创建控制器实例
+ $module = controller(CONTROLLER_NAME, CONTROLLER_PATH);
+ }
+
+ if (!$module) {
+ if ('4e5e5d7364f443e28fbf0d3ae744a59a' == CONTROLLER_NAME) {
+ header("Content-type:image/png");
+ exit(base64_decode(App::logo()));
+ }
+
+ // 是否定义Empty控制器
+ $module = A('Empty');
+ if (!$module) {
+ E(L('_CONTROLLER_NOT_EXIST_') . ':' . CONTROLLER_NAME);
+ }
+ }
+
+ // 获取当前操作名 支持动态路由
+ if (!isset($action)) {
+ $action = ACTION_NAME . C('ACTION_SUFFIX');
+ }
+ try {
+ self::invokeAction($module, $action);
+ } catch (\ReflectionException $e) {
+ // 方法调用发生异常后 引导到__call方法处理
+ $method = new \ReflectionMethod($module, '__call');
+ $method->invokeArgs($module, array($action, ''));
+ }
+ return;
+ }
+
+ public static function invokeAction($module, $action)
+ {
+ if (!preg_match('/^[A-Za-z](\w)*$/', $action)) {
+ // 非法操作
+ throw new \ReflectionException();
+ }
+ //执行当前操作
+ $method = new \ReflectionMethod($module, $action);
+ if ($method->isPublic() && !$method->isStatic()) {
+ $class = new \ReflectionClass($module);
+ // 前置操作
+ if ($class->hasMethod('_before_' . $action)) {
+ $before = $class->getMethod('_before_' . $action);
+ if ($before->isPublic()) {
+ $before->invoke($module);
+ }
+ }
+ // URL参数绑定检测
+ if ($method->getNumberOfParameters() > 0 && C('URL_PARAMS_BIND')) {
+ switch ($_SERVER['REQUEST_METHOD']) {
+ case 'POST':
+ $vars = array_merge($_GET, $_POST);
+ break;
+ case 'PUT':
+ parse_str(file_get_contents('php://input'), $vars);
+ break;
+ default:
+ $vars = $_GET;
+ }
+ $params = $method->getParameters();
+ $paramsBindType = C('URL_PARAMS_BIND_TYPE');
+ foreach ($params as $param) {
+ $name = $param->getName();
+ if (1 == $paramsBindType && !empty($vars)) {
+ $args[] = array_shift($vars);
+ } elseif (0 == $paramsBindType && isset($vars[$name])) {
+ $args[] = $vars[$name];
+ } elseif ($param->isDefaultValueAvailable()) {
+ $args[] = $param->getDefaultValue();
+ } else {
+ E(L('_PARAM_ERROR_') . ':' . $name);
+ }
+ }
+ // 开启绑定参数过滤机制
+ if (C('URL_PARAMS_FILTER')) {
+ $filters = C('URL_PARAMS_FILTER_TYPE') ?: C('DEFAULT_FILTER');
+ if ($filters) {
+ $filters = explode(',', $filters);
+ foreach ($filters as $filter) {
+ $args = array_map_recursive($filter, $args); // 参数过滤
+ }
+ }
+ }
+ array_walk_recursive($args, 'think_filter');
+ $method->invokeArgs($module, $args);
+ } else {
+ $method->invoke($module);
+ }
+ // 后置操作
+ if ($class->hasMethod('_after_' . $action)) {
+ $after = $class->getMethod('_after_' . $action);
+ if ($after->isPublic()) {
+ $after->invoke($module);
+ }
+ }
+ } else {
+ // 操作方法不是Public 抛出异常
+ throw new \ReflectionException();
+ }
+ }
+
+ /**
+ * 运行应用实例 入口文件使用的快捷方法
+ * @access public
+ * @return void
+ */
+ public static function run()
+ {
+ // 加载动态应用公共文件和配置
+ load_ext_file(COMMON_PATH);
+ // 应用初始化标签
+ Hook::listen('app_init');
+ App::init();
+ // 应用开始标签
+ Hook::listen('app_begin');
+ // Session初始化
+ if (!IS_CLI) {
+ session(C('SESSION_OPTIONS'));
+ }
+ // 记录应用初始化时间
+ G('initTime');
+ App::exec();
+ // 应用结束标签
+ Hook::listen('app_end');
+ return;
+ }
+
+ public static function logo()
+ {
+ return 'iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjVERDVENkZGQjkyNDExRTE5REY3RDQ5RTQ2RTRDQUJCIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjVERDVENzAwQjkyNDExRTE5REY3RDQ5RTQ2RTRDQUJCIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NURENUQ2RkRCOTI0MTFFMTlERjdENDlFNDZFNENBQkIiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NURENUQ2RkVCOTI0MTFFMTlERjdENDlFNDZFNENBQkIiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz5fx6IRAAAMCElEQVR42sxae3BU1Rk/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';
+ }
+}
diff --git a/Framework/Library/Think/Auth.class.php b/Framework/Library/Think/Auth.class.php
new file mode 100644
index 00000000..d5aca936
--- /dev/null
+++ b/Framework/Library/Think/Auth.class.php
@@ -0,0 +1,244 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * 权限认证类
+ * 功能特性:
+ * 1,是对规则进行认证,不是对节点进行认证。用户可以把节点当作规则名称实现对节点进行认证。
+ * $auth=new Auth(); $auth->check('规则名称','用户id')
+ * 2,可以同时对多条规则进行认证,并设置多条规则的关系(or或者and)
+ * $auth=new Auth(); $auth->check('规则1,规则2','用户id','and')
+ * 第三个参数为and时表示,用户需要同时具有规则1和规则2的权限。 当第三个参数为or时,表示用户值需要具备其中一个条件即可。默认为or
+ * 3,一个用户可以属于多个用户组(think_auth_group_access表 定义了用户所属用户组)。我们需要设置每个用户组拥有哪些规则(think_auth_group 定义了用户组权限)
+ *
+ * 4,支持规则表达式。
+ * 在think_auth_rule 表中定义一条规则时,如果type为1, condition字段就可以定义规则表达式。 如定义{score}>5 and {score}<100 表示用户的分数在5-100之间时这条规则才会通过。
+ */
+
+//数据库
+/*
+-- ----------------------------
+-- think_auth_rule,规则表,
+-- id:主键,name:规则唯一标识, title:规则中文名称 status 状态:为1正常,为0禁用,condition:规则表达式,为空表示存在就验证,不为空表示按照条件验证
+-- ----------------------------
+DROP TABLE IF EXISTS `think_auth_rule`;
+CREATE TABLE `think_auth_rule` (
+`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+`name` char(80) NOT NULL DEFAULT '',
+`title` char(20) NOT NULL DEFAULT '',
+`type` tinyint(1) NOT NULL DEFAULT '1',
+`status` tinyint(1) NOT NULL DEFAULT '1',
+`condition` char(100) NOT NULL DEFAULT '', # 规则附件条件,满足附加条件的规则,才认为是有效的规则
+PRIMARY KEY (`id`),
+UNIQUE KEY `name` (`name`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+-- ----------------------------
+-- think_auth_group 用户组表,
+-- id:主键, title:用户组中文名称, rules:用户组拥有的规则id, 多个规则","隔开,status 状态:为1正常,为0禁用
+-- ----------------------------
+DROP TABLE IF EXISTS `think_auth_group`;
+CREATE TABLE `think_auth_group` (
+`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+`title` char(100) NOT NULL DEFAULT '',
+`status` tinyint(1) NOT NULL DEFAULT '1',
+`rules` char(80) NOT NULL DEFAULT '',
+PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+-- ----------------------------
+-- think_auth_group_access 用户组明细表
+-- uid:用户id,group_id:用户组id
+-- ----------------------------
+DROP TABLE IF EXISTS `think_auth_group_access`;
+CREATE TABLE `think_auth_group_access` (
+`uid` mediumint(8) unsigned NOT NULL,
+`group_id` mediumint(8) unsigned NOT NULL,
+UNIQUE KEY `uid_group_id` (`uid`,`group_id`),
+KEY `uid` (`uid`),
+KEY `group_id` (`group_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+ */
+
+class Auth
+{
+
+ //默认配置
+ protected $_config = array(
+ 'AUTH_ON' => true, // 认证开关
+ 'AUTH_TYPE' => 1, // 认证方式,1为实时认证;2为登录认证。
+ 'AUTH_GROUP' => 'auth_group', // 用户组数据表名
+ 'AUTH_GROUP_ACCESS' => 'auth_group_access', // 用户-用户组关系表
+ 'AUTH_RULE' => 'auth_rule', // 权限规则表
+ 'AUTH_USER' => 'member', // 用户信息表
+ );
+
+ public function __construct()
+ {
+ $prefix = C('DB_PREFIX');
+ $this->_config['AUTH_GROUP'] = $prefix . $this->_config['AUTH_GROUP'];
+ $this->_config['AUTH_RULE'] = $prefix . $this->_config['AUTH_RULE'];
+ $this->_config['AUTH_USER'] = $prefix . $this->_config['AUTH_USER'];
+ $this->_config['AUTH_GROUP_ACCESS'] = $prefix . $this->_config['AUTH_GROUP_ACCESS'];
+ if (C('AUTH_CONFIG')) {
+ //可设置配置项 AUTH_CONFIG, 此配置项为数组。
+ $this->_config = array_merge($this->_config, C('AUTH_CONFIG'));
+ }
+ }
+
+ /**
+ * 检查权限
+ * @param name string|array 需要验证的规则列表,支持逗号分隔的权限规则或索引数组
+ * @param uid int 认证用户的id
+ * @param string mode 执行check的模式
+ * @param relation string 如果为 'or' 表示满足任一条规则即通过验证;如果为 'and'则表示需满足所有规则才能通过验证
+ * @return boolean 通过验证返回true;失败返回false
+ */
+ public function check($name, $uid, $type = 1, $mode = 'url', $relation = 'or')
+ {
+ if (!$this->_config['AUTH_ON']) {
+ return true;
+ }
+
+ $authList = $this->getAuthList($uid, $type); //获取用户需要验证的所有有效规则列表
+ if (is_string($name)) {
+ $name = strtolower($name);
+ if (strpos($name, ',') !== false) {
+ $name = explode(',', $name);
+ } else {
+ $name = array($name);
+ }
+ }
+ $list = array(); //保存验证通过的规则名
+ if ('url' == $mode) {
+ $REQUEST = unserialize(strtolower(serialize($_REQUEST)));
+ }
+ foreach ($authList as $auth) {
+ $query = preg_replace('/^.+\?/U', '', $auth);
+ if ('url' == $mode && $query != $auth) {
+ parse_str($query, $param); //解析规则中的param
+ $intersect = array_intersect_assoc($REQUEST, $param);
+ $auth = preg_replace('/\?.*$/U', '', $auth);
+ if (in_array($auth, $name) && $intersect == $param) {
+ //如果节点相符且url参数满足
+ $list[] = $auth;
+ }
+ } else if (in_array($auth, $name)) {
+ $list[] = $auth;
+ }
+ }
+ if ('or' == $relation and !empty($list)) {
+ return true;
+ }
+ $diff = array_diff($name, $list);
+ if ('and' == $relation and empty($diff)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 根据用户id获取用户组,返回值为数组
+ * @param uid int 用户id
+ * @return array 用户所属的用户组 array(
+ * array('uid'=>'用户id','group_id'=>'用户组id','title'=>'用户组名称','rules'=>'用户组拥有的规则id,多个,号隔开'),
+ * ...)
+ */
+ public function getGroups($uid)
+ {
+ static $groups = array();
+ if (isset($groups[$uid])) {
+ return $groups[$uid];
+ }
+
+ $user_groups = M()
+ ->table($this->_config['AUTH_GROUP_ACCESS'] . ' a')
+ ->where("a.uid='$uid' and g.status='1'")
+ ->join($this->_config['AUTH_GROUP'] . " g on a.group_id=g.id")
+ ->field('uid,group_id,title,rules')->select();
+ $groups[$uid] = $user_groups ?: array();
+ return $groups[$uid];
+ }
+
+ /**
+ * 获得权限列表
+ * @param integer $uid 用户id
+ * @param integer $type
+ */
+ protected function getAuthList($uid, $type)
+ {
+ static $_authList = array(); //保存用户验证通过的权限列表
+ $t = implode(',', (array) $type);
+ if (isset($_authList[$uid . $t])) {
+ return $_authList[$uid . $t];
+ }
+ if (2 == $this->_config['AUTH_TYPE'] && isset($_SESSION['_AUTH_LIST_' . $uid . $t])) {
+ return $_SESSION['_AUTH_LIST_' . $uid . $t];
+ }
+
+ //读取用户所属用户组
+ $groups = $this->getGroups($uid);
+ $ids = array(); //保存用户所属用户组设置的所有权限规则id
+ foreach ($groups as $g) {
+ $ids = array_merge($ids, explode(',', trim($g['rules'], ',')));
+ }
+ $ids = array_unique($ids);
+ if (empty($ids)) {
+ $_authList[$uid . $t] = array();
+ return array();
+ }
+
+ $map = array(
+ 'id' => array('in', $ids),
+ 'type' => $type,
+ 'status' => 1,
+ );
+ //读取用户组所有权限规则
+ $rules = M()->table($this->_config['AUTH_RULE'])->where($map)->field('condition,name')->select();
+
+ //循环规则,判断结果。
+ $authList = array(); //
+ foreach ($rules as $rule) {
+ if (!empty($rule['condition'])) {
+ //根据condition进行验证
+ $user = $this->getUserInfo($uid); //获取用户信息,一维数组
+
+ $command = preg_replace('/\{(\w*?)\}/', '$user[\'\\1\']', $rule['condition']);
+ //dump($command);//debug
+ @(eval('$condition=(' . $command . ');'));
+ if ($condition) {
+ $authList[] = strtolower($rule['name']);
+ }
+ } else {
+ //只要存在就记录
+ $authList[] = strtolower($rule['name']);
+ }
+ }
+ $_authList[$uid . $t] = $authList;
+ if (2 == $this->_config['AUTH_TYPE']) {
+ //规则列表结果保存到session
+ $_SESSION['_AUTH_LIST_' . $uid . $t] = $authList;
+ }
+ return array_unique($authList);
+ }
+
+ /**
+ * 获得用户资料,根据自己的情况读取数据库
+ */
+ protected function getUserInfo($uid)
+ {
+ static $userinfo = array();
+ if (!isset($userinfo[$uid])) {
+ $userinfo[$uid] = M()->where(array('uid' => $uid))->table($this->_config['AUTH_USER'])->find();
+ }
+ return $userinfo[$uid];
+ }
+
+}
diff --git a/Framework/Library/Think/Behavior.class.php b/Framework/Library/Think/Behavior.class.php
new file mode 100644
index 00000000..624a76d1
--- /dev/null
+++ b/Framework/Library/Think/Behavior.class.php
@@ -0,0 +1,26 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * ThinkPHP Behavior基础类
+ */
+abstract class Behavior
+{
+ /**
+ * 执行行为 run方法是Behavior唯一的接口
+ * @access public
+ * @param mixed $params 行为参数
+ * @return void
+ */
+ abstract public function run(&$params);
+
+}
diff --git a/Framework/Library/Think/Build.class.php b/Framework/Library/Think/Build.class.php
new file mode 100644
index 00000000..a157591b
--- /dev/null
+++ b/Framework/Library/Think/Build.class.php
@@ -0,0 +1,197 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * 用于ThinkPHP的自动生成
+ */
+class Build
+{
+
+ protected static $controller = ''配置值'\n);" : '');
+ }
+
+ // 写入模块配置文件
+ if (!is_file(APP_PATH . $module . '/Conf/config' . CONF_EXT)) {
+ file_put_contents(APP_PATH . $module . '/Conf/config' . CONF_EXT, '.php' == CONF_EXT ? "'配置值'\n);" : '');
+ }
+
+ // 自动生成控制器类
+ self::buildController($module, defined('BUILD_CONTROLLER_LIST') ? BUILD_CONTROLLER_LIST : C('DEFAULT_CONTROLLER'));
+
+ // 自动生成模型类
+ if (defined('BUILD_MODEL_LIST')) {
+ self::buildModel($module, BUILD_MODEL_LIST);
+ }
+ } else {
+ header('Content-Type:text/html; charset=utf-8');
+ exit('应用目录[' . APP_PATH . ']不可写,目录无法自动生成! 请手动生成项目目录~');
+ }
+ }
+
+ // 检查缓存目录(Runtime) 如果不存在则自动创建
+ public static function buildRuntime()
+ {
+ if (!is_dir(RUNTIME_PATH)) {
+ mkdir(RUNTIME_PATH);
+ } elseif (!is_writeable(RUNTIME_PATH)) {
+ header('Content-Type:text/html; charset=utf-8');
+ exit('目录 [ ' . RUNTIME_PATH . ' ] 不可写!');
+ }
+ mkdir(CACHE_PATH); // 模板缓存目录
+ if (!is_dir(LOG_PATH)) {
+ mkdir(LOG_PATH);
+ }
+ // 日志目录
+ if (!is_dir(TEMP_PATH)) {
+ mkdir(TEMP_PATH);
+ }
+ // 数据缓存目录
+ if (!is_dir(DATA_PATH)) {
+ mkdir(DATA_PATH);
+ }
+ // 数据文件目录
+ return true;
+ }
+
+ // 创建控制器类
+ public static function buildController($module, $controllers)
+ {
+ $list = is_array($controllers) ? $controllers : explode(',', $controllers);
+ $hello = '$this->show(\' :) 欢迎使用 ThinkPHP !
版本 V{$Think.version}
\',\'utf-8\');';
+
+ foreach ($list as $controller) {
+ $hello = C('DEFAULT_CONTROLLER') == $controller ? $hello : '';
+ $file = APP_PATH . $module . '/Controller/' . $controller . 'Controller' . EXT;
+ if (!is_file($file)) {
+ $content = str_replace(array('[MODULE]', '[CONTROLLER]', '[CONTENT]'), array($module, $controller, $hello), self::$controller);
+ if (!C('APP_USE_NAMESPACE')) {
+ $content = preg_replace('/namespace\s(.*?);/', '', $content, 1);
+ }
+ $dir = dirname($file);
+ if (!is_dir($dir)) {
+ mkdir($dir, 0755, true);
+ }
+ file_put_contents($file, $content);
+ }
+ }
+ }
+
+ // 创建模型类
+ public static function buildModel($module, $models)
+ {
+ $list = is_array($models) ? $models : explode(',', $models);
+ foreach ($list as $model) {
+ $file = APP_PATH . $module . '/Model/' . $model . 'Model' . EXT;
+ if (!is_file($file)) {
+ $content = str_replace(array('[MODULE]', '[MODEL]'), array($module, $model), self::$model);
+ if (!C('APP_USE_NAMESPACE')) {
+ $content = preg_replace('/namespace\s(.*?);/', '', $content, 1);
+ }
+ $dir = dirname($file);
+ if (!is_dir($dir)) {
+ mkdir($dir, 0755, true);
+ }
+ file_put_contents($file, $content);
+ }
+ }
+ }
+
+ // 生成目录安全文件
+ public static function buildDirSecure($dirs = array())
+ {
+ // 目录安全写入(默认开启)
+ defined('BUILD_DIR_SECURE') or define('BUILD_DIR_SECURE', true);
+ if (BUILD_DIR_SECURE) {
+ defined('DIR_SECURE_FILENAME') or define('DIR_SECURE_FILENAME', 'index.html');
+ defined('DIR_SECURE_CONTENT') or define('DIR_SECURE_CONTENT', ' ');
+ // 自动写入目录安全文件
+ $content = DIR_SECURE_CONTENT;
+ $files = explode(',', DIR_SECURE_FILENAME);
+ foreach ($files as $filename) {
+ foreach ($dirs as $dir) {
+ file_put_contents($dir . $filename, $content);
+ }
+ }
+ }
+ }
+}
diff --git a/Framework/Library/Think/Cache.class.php b/Framework/Library/Think/Cache.class.php
new file mode 100644
index 00000000..c8c59b9c
--- /dev/null
+++ b/Framework/Library/Think/Cache.class.php
@@ -0,0 +1,146 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * 缓存管理类
+ */
+class Cache
+{
+
+ /**
+ * 操作句柄
+ * @var string
+ * @access protected
+ */
+ protected $handler;
+
+ /**
+ * 缓存连接参数
+ * @var integer
+ * @access protected
+ */
+ protected $options = array();
+
+ /**
+ * 连接缓存
+ * @access public
+ * @param string $type 缓存类型
+ * @param array $options 配置数组
+ * @return object
+ */
+ public function connect($type = '', $options = array())
+ {
+ if (empty($type)) {
+ $type = C('DATA_CACHE_TYPE');
+ }
+
+ $class = strpos($type, '\\') ? $type : 'Think\\Cache\\Driver\\' . ucwords(strtolower($type));
+ if (class_exists($class)) {
+ $cache = new $class($options);
+ } else {
+ E(L('_CACHE_TYPE_INVALID_') . ':' . $type);
+ }
+
+ return $cache;
+ }
+
+ /**
+ * 取得缓存类实例
+ * @static
+ * @access public
+ * @return mixed
+ */
+ public static function getInstance($type = '', $options = array())
+ {
+ static $_instance = array();
+ $guid = $type . to_guid_string($options);
+ if (!isset($_instance[$guid])) {
+ $obj = new Cache();
+ $_instance[$guid] = $obj->connect($type, $options);
+ }
+ return $_instance[$guid];
+ }
+
+ public function __get($name)
+ {
+ return $this->get($name);
+ }
+
+ public function __set($name, $value)
+ {
+ return $this->set($name, $value);
+ }
+
+ public function __unset($name)
+ {
+ $this->rm($name);
+ }
+ public function setOptions($name, $value)
+ {
+ $this->options[$name] = $value;
+ }
+
+ public function getOptions($name)
+ {
+ return $this->options[$name];
+ }
+
+ /**
+ * 队列缓存
+ * @access protected
+ * @param string $key 队列名
+ * @return mixed
+ */
+ //
+ protected function queue($key)
+ {
+ static $_handler = array(
+ 'file' => array('F', 'F'),
+ 'xcache' => array('xcache_get', 'xcache_set'),
+ 'apc' => array('apc_fetch', 'apc_store'),
+ );
+ $queue = isset($this->options['queue']) ? $this->options['queue'] : 'file';
+ $fun = isset($_handler[$queue]) ? $_handler[$queue] : $_handler['file'];
+ $queue_name = isset($this->options['queue_name']) ? $this->options['queue_name'] : 'think_queue';
+ $value = $fun[0]($queue_name);
+ if (!$value) {
+ $value = array();
+ }
+ // 进列
+ if (false === array_search($key, $value)) {
+ array_push($value, $key);
+ }
+
+ if (count($value) > $this->options['length']) {
+ // 出列
+ $key = array_shift($value);
+ // 删除缓存
+ $this->rm($key);
+ if (APP_DEBUG) {
+ //调试模式下,记录出列次数
+ N($queue_name . '_out_times', 1);
+ }
+ }
+ return $fun[1]($queue_name, $value);
+ }
+
+ public function __call($method, $args)
+ {
+ //调用缓存类型自己的方法
+ if (method_exists($this->handler, $method)) {
+ return call_user_func_array(array($this->handler, $method), $args);
+ } else {
+ E(__CLASS__ . ':' . $method . L('_METHOD_NOT_EXIST_'));
+ return;
+ }
+ }
+}
diff --git a/Framework/Library/Think/Cache/Driver/Apachenote.class.php b/Framework/Library/Think/Cache/Driver/Apachenote.class.php
new file mode 100644
index 00000000..125f5a53
--- /dev/null
+++ b/Framework/Library/Think/Cache/Driver/Apachenote.class.php
@@ -0,0 +1,132 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Cache\Driver;
+
+use Think\Cache;
+
+/**
+ * Apachenote缓存驱动
+ */
+class Apachenote extends Cache
+{
+
+ /**
+ * 架构函数
+ * @param array $options 缓存参数
+ * @access public
+ */
+ public function __construct($options = array())
+ {
+ if (!empty($options)) {
+ $this->options = $options;
+ }
+ if (empty($options)) {
+ $options = array(
+ 'host' => '127.0.0.1',
+ 'port' => 1042,
+ 'timeout' => 10,
+ );
+ }
+ $this->options = $options;
+ $this->options['prefix'] = isset($options['prefix']) ? $options['prefix'] : C('DATA_CACHE_PREFIX');
+ $this->options['length'] = isset($options['length']) ? $options['length'] : 0;
+ $this->handler = null;
+ $this->open();
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return mixed
+ */
+ public function get($name)
+ {
+ $this->open();
+ $name = $this->options['prefix'] . $name;
+ $s = 'F' . pack('N', strlen($name)) . $name;
+ fwrite($this->handler, $s);
+
+ for ($data = '';!feof($this->handler);) {
+ $data .= fread($this->handler, 4096);
+ }
+ N('cache_read', 1);
+ $this->close();
+ return '' === $data ? '' : unserialize($data);
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @return boolean
+ */
+ public function set($name, $value)
+ {
+ N('cache_write', 1);
+ $this->open();
+ $value = serialize($value);
+ $name = $this->options['prefix'] . $name;
+ $s = 'S' . pack('NN', strlen($name), strlen($value)) . $name . $value;
+
+ fwrite($this->handler, $s);
+ $ret = fgets($this->handler);
+ $this->close();
+ if ("OK\n" === $ret) {
+ if ($this->options['length'] > 0) {
+ // 记录缓存队列
+ $this->queue($name);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return boolean
+ */
+ public function rm($name)
+ {
+ $this->open();
+ $name = $this->options['prefix'] . $name;
+ $s = 'D' . pack('N', strlen($name)) . $name;
+ fwrite($this->handler, $s);
+ $ret = fgets($this->handler);
+ $this->close();
+ return "OK\n" === $ret;
+ }
+
+ /**
+ * 关闭缓存
+ * @access private
+ */
+ private function close()
+ {
+ fclose($this->handler);
+ $this->handler = false;
+ }
+
+ /**
+ * 打开缓存
+ * @access private
+ */
+ private function open()
+ {
+ if (!is_resource($this->handler)) {
+ $this->handler = fsockopen($this->options['host'], $this->options['port'], $_, $_, $this->options['timeout']);
+ }
+ }
+
+}
diff --git a/Framework/Library/Think/Cache/Driver/Apc.class.php b/Framework/Library/Think/Cache/Driver/Apc.class.php
new file mode 100644
index 00000000..5135d00e
--- /dev/null
+++ b/Framework/Library/Think/Cache/Driver/Apc.class.php
@@ -0,0 +1,93 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Cache\Driver;
+
+use Think\Cache;
+
+/**
+ * Apc缓存驱动
+ */
+class Apc extends Cache
+{
+
+ /**
+ * 架构函数
+ * @param array $options 缓存参数
+ * @access public
+ */
+ public function __construct($options = array())
+ {
+ if (!function_exists('apc_cache_info')) {
+ E(L('_NOT_SUPPORT_') . ':Apc');
+ }
+ $this->options['prefix'] = isset($options['prefix']) ? $options['prefix'] : C('DATA_CACHE_PREFIX');
+ $this->options['length'] = isset($options['length']) ? $options['length'] : 0;
+ $this->options['expire'] = isset($options['expire']) ? $options['expire'] : C('DATA_CACHE_TIME');
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return mixed
+ */
+ public function get($name)
+ {
+ N('cache_read', 1);
+ return apc_fetch($this->options['prefix'] . $name);
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer $expire 有效时间(秒)
+ * @return boolean
+ */
+ public function set($name, $value, $expire = null)
+ {
+ N('cache_write', 1);
+ if (is_null($expire)) {
+ $expire = $this->options['expire'];
+ }
+ $name = $this->options['prefix'] . $name;
+ if ($result = apc_store($name, $value, $expire)) {
+ if ($this->options['length'] > 0) {
+ // 记录缓存队列
+ $this->queue($name);
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return boolean
+ */
+ public function rm($name)
+ {
+ return apc_delete($this->options['prefix'] . $name);
+ }
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return boolean
+ */
+ public function clear()
+ {
+ return apc_clear_cache();
+ }
+
+}
diff --git a/Framework/Library/Think/Cache/Driver/Db.class.php b/Framework/Library/Think/Cache/Driver/Db.class.php
new file mode 100644
index 00000000..cb319bae
--- /dev/null
+++ b/Framework/Library/Think/Cache/Driver/Db.class.php
@@ -0,0 +1,146 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Cache\Driver;
+
+use Think\Cache;
+
+/**
+ * 数据库方式缓存驱动
+ * CREATE TABLE think_cache (
+ * cachekey varchar(255) NOT NULL,
+ * expire int(11) NOT NULL,
+ * data blob,
+ * datacrc int(32),
+ * UNIQUE KEY `cachekey` (`cachekey`)
+ * );
+ */
+class Db extends Cache
+{
+
+ /**
+ * 架构函数
+ * @param array $options 缓存参数
+ * @access public
+ */
+ public function __construct($options = array())
+ {
+ if (empty($options)) {
+ $options = array(
+ 'table' => C('DATA_CACHE_TABLE'),
+ );
+ }
+ $this->options = $options;
+ $this->options['prefix'] = isset($options['prefix']) ? $options['prefix'] : C('DATA_CACHE_PREFIX');
+ $this->options['length'] = isset($options['length']) ? $options['length'] : 0;
+ $this->options['expire'] = isset($options['expire']) ? $options['expire'] : C('DATA_CACHE_TIME');
+ $this->handler = \Think\Db::getInstance();
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return mixed
+ */
+ public function get($name)
+ {
+ $name = $this->options['prefix'] . addslashes($name);
+ N('cache_read', 1);
+ $result = $this->handler->query('SELECT `data`,`datacrc` FROM `' . $this->options['table'] . '` WHERE `cachekey`=\'' . $name . '\' AND (`expire` =0 OR `expire`>' . time() . ') LIMIT 0,1');
+ if (false !== $result) {
+ $result = $result[0];
+ if (C('DATA_CACHE_CHECK')) {
+//开启数据校验
+ if (md5($result['data']) != $result['datacrc']) { //校验错误
+ return false;
+ }
+ }
+ $content = $result['data'];
+ if (C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) {
+ //启用数据压缩
+ $content = gzuncompress($content);
+ }
+ $content = unserialize($content);
+ return $content;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer $expire 有效时间(秒)
+ * @return boolean
+ */
+ public function set($name, $value, $expire = null)
+ {
+ $data = serialize($value);
+ $name = $this->options['prefix'] . addslashes($name);
+ N('cache_write', 1);
+ if (C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) {
+ //数据压缩
+ $data = gzcompress($data, 3);
+ }
+ if (C('DATA_CACHE_CHECK')) {
+//开启数据校验
+ $crc = md5($data);
+ } else {
+ $crc = '';
+ }
+ if (is_null($expire)) {
+ $expire = $this->options['expire'];
+ }
+ $expire = (0 == $expire) ? 0 : (time() + $expire); //缓存有效期为0表示永久缓存
+ $result = $this->handler->query('select `cachekey` from `' . $this->options['table'] . '` where `cachekey`=\'' . $name . '\' limit 0,1');
+ if (!empty($result)) {
+ //更新记录
+ $result = $this->handler->execute('UPDATE ' . $this->options['table'] . ' SET data=\'' . $data . '\' ,datacrc=\'' . $crc . '\',expire=' . $expire . ' WHERE `cachekey`=\'' . $name . '\'');
+ } else {
+ //新增记录
+ $result = $this->handler->execute('INSERT INTO ' . $this->options['table'] . ' (`cachekey`,`data`,`datacrc`,`expire`) VALUES (\'' . $name . '\',\'' . $data . '\',\'' . $crc . '\',' . $expire . ')');
+ }
+ if ($result) {
+ if ($this->options['length'] > 0) {
+ // 记录缓存队列
+ $this->queue($name);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return boolean
+ */
+ public function rm($name)
+ {
+ $name = $this->options['prefix'] . addslashes($name);
+ return $this->handler->execute('DELETE FROM `' . $this->options['table'] . '` WHERE `cachekey`=\'' . $name . '\'');
+ }
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return boolean
+ */
+ public function clear()
+ {
+ return $this->handler->execute('TRUNCATE TABLE `' . $this->options['table'] . '`');
+ }
+
+}
diff --git a/Framework/Library/Think/Cache/Driver/Eaccelerator.class.php b/Framework/Library/Think/Cache/Driver/Eaccelerator.class.php
new file mode 100644
index 00000000..6cd7d4c0
--- /dev/null
+++ b/Framework/Library/Think/Cache/Driver/Eaccelerator.class.php
@@ -0,0 +1,82 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Cache\Driver;
+
+use Think\Cache;
+
+/**
+ * Eaccelerator缓存驱动
+ */
+class Eaccelerator extends Cache
+{
+
+ /**
+ * 架构函数
+ * @param array $options 缓存参数
+ * @access public
+ */
+ public function __construct($options = array())
+ {
+ $this->options['expire'] = isset($options['expire']) ? $options['expire'] : C('DATA_CACHE_TIME');
+ $this->options['prefix'] = isset($options['prefix']) ? $options['prefix'] : C('DATA_CACHE_PREFIX');
+ $this->options['length'] = isset($options['length']) ? $options['length'] : 0;
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return mixed
+ */
+ public function get($name)
+ {
+ N('cache_read', 1);
+ return eaccelerator_get($this->options['prefix'] . $name);
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer $expire 有效时间(秒)
+ * @return boolean
+ */
+ public function set($name, $value, $expire = null)
+ {
+ N('cache_write', 1);
+ if (is_null($expire)) {
+ $expire = $this->options['expire'];
+ }
+ $name = $this->options['prefix'] . $name;
+ eaccelerator_lock($name);
+ if (eaccelerator_put($name, $value, $expire)) {
+ if ($this->options['length'] > 0) {
+ // 记录缓存队列
+ $this->queue($name);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return boolean
+ */
+ public function rm($name)
+ {
+ return eaccelerator_rm($this->options['prefix'] . $name);
+ }
+
+}
diff --git a/Framework/Library/Think/Cache/Driver/File.class.php b/Framework/Library/Think/Cache/Driver/File.class.php
new file mode 100644
index 00000000..54d8fd66
--- /dev/null
+++ b/Framework/Library/Think/Cache/Driver/File.class.php
@@ -0,0 +1,195 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Cache\Driver;
+
+use Think\Cache;
+
+/**
+ * 文件类型缓存类
+ */
+class File extends Cache
+{
+
+ /**
+ * 架构函数
+ * @access public
+ */
+ public function __construct($options = array())
+ {
+ if (!empty($options)) {
+ $this->options = $options;
+ }
+ $this->options['temp'] = !empty($options['temp']) ? $options['temp'] : C('DATA_CACHE_PATH');
+ $this->options['prefix'] = isset($options['prefix']) ? $options['prefix'] : C('DATA_CACHE_PREFIX');
+ $this->options['expire'] = isset($options['expire']) ? $options['expire'] : C('DATA_CACHE_TIME');
+ $this->options['length'] = isset($options['length']) ? $options['length'] : 0;
+ if (substr($this->options['temp'], -1) != '/') {
+ $this->options['temp'] .= '/';
+ }
+
+ $this->init();
+ }
+
+ /**
+ * 初始化检查
+ * @access private
+ * @return boolean
+ */
+ private function init()
+ {
+ // 创建应用缓存目录
+ if (!is_dir($this->options['temp'])) {
+ mkdir($this->options['temp']);
+ }
+ }
+
+ /**
+ * 取得变量的存储文件名
+ * @access private
+ * @param string $name 缓存变量名
+ * @return string
+ */
+ private function filename($name)
+ {
+ $name = md5(C('DATA_CACHE_KEY') . $name);
+ if (C('DATA_CACHE_SUBDIR')) {
+ // 使用子目录
+ $dir = '';
+ for ($i = 0; $i < C('DATA_PATH_LEVEL'); $i++) {
+ $dir .= $name{$i} . '/';
+ }
+ if (!is_dir($this->options['temp'] . $dir)) {
+ mkdir($this->options['temp'] . $dir, 0755, true);
+ }
+ $filename = $dir . $this->options['prefix'] . $name . '.php';
+ } else {
+ $filename = $this->options['prefix'] . $name . '.php';
+ }
+ return $this->options['temp'] . $filename;
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return mixed
+ */
+ public function get($name)
+ {
+ $filename = $this->filename($name);
+ if (!is_file($filename)) {
+ return false;
+ }
+ N('cache_read', 1);
+ $content = file_get_contents($filename);
+ if (false !== $content) {
+ $expire = (int) substr($content, 8, 12);
+ if (0 != $expire && time() > filemtime($filename) + $expire) {
+ //缓存过期删除缓存文件
+ unlink($filename);
+ return false;
+ }
+ if (C('DATA_CACHE_CHECK')) {
+//开启数据校验
+ $check = substr($content, 20, 32);
+ $content = substr($content, 52, -3);
+ if (md5($content) != $check) {
+//校验错误
+ return false;
+ }
+ } else {
+ $content = substr($content, 20, -3);
+ }
+ if (C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) {
+ //启用数据压缩
+ $content = gzuncompress($content);
+ }
+ $content = unserialize($content);
+ return $content;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param int $expire 有效时间 0为永久
+ * @return boolean
+ */
+ public function set($name, $value, $expire = null)
+ {
+ N('cache_write', 1);
+ if (is_null($expire)) {
+ $expire = $this->options['expire'];
+ }
+ $filename = $this->filename($name);
+ $data = serialize($value);
+ if (C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) {
+ //数据压缩
+ $data = gzcompress($data, 3);
+ }
+ if (C('DATA_CACHE_CHECK')) {
+//开启数据校验
+ $check = md5($data);
+ } else {
+ $check = '';
+ }
+ $data = "";
+ $result = file_put_contents($filename, $data);
+ if ($result) {
+ if ($this->options['length'] > 0) {
+ // 记录缓存队列
+ $this->queue($name);
+ }
+ clearstatcache();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return boolean
+ */
+ public function rm($name)
+ {
+ return unlink($this->filename($name));
+ }
+
+ /**
+ * 清除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return boolean
+ */
+ public function clear()
+ {
+ $path = $this->options['temp'];
+ $files = scandir($path);
+ if ($files) {
+ foreach ($files as $file) {
+ if ('.' != $file && '..' != $file && is_dir($path . $file)) {
+ array_map('unlink', glob($path . $file . '/*.*'));
+ } elseif (is_file($path . $file)) {
+ unlink($path . $file);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/Framework/Library/Think/Cache/Driver/Memcache.class.php b/Framework/Library/Think/Cache/Driver/Memcache.class.php
new file mode 100644
index 00000000..b7e7b046
--- /dev/null
+++ b/Framework/Library/Think/Cache/Driver/Memcache.class.php
@@ -0,0 +1,109 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Cache\Driver;
+
+use Think\Cache;
+
+/**
+ * Memcache缓存驱动
+ */
+class Memcache extends Cache
+{
+
+ /**
+ * 架构函数
+ * @param array $options 缓存参数
+ * @access public
+ */
+ public function __construct($options = array())
+ {
+ if (!extension_loaded('memcache')) {
+ E(L('_NOT_SUPPORT_') . ':memcache');
+ }
+
+ $options = array_merge(array(
+ 'host' => C('MEMCACHE_HOST') ?: '127.0.0.1',
+ 'port' => C('MEMCACHE_PORT') ?: 11211,
+ 'timeout' => C('DATA_CACHE_TIMEOUT') ?: false,
+ 'persistent' => false,
+ ), $options);
+
+ $this->options = $options;
+ $this->options['expire'] = isset($options['expire']) ? $options['expire'] : C('DATA_CACHE_TIME');
+ $this->options['prefix'] = isset($options['prefix']) ? $options['prefix'] : C('DATA_CACHE_PREFIX');
+ $this->options['length'] = isset($options['length']) ? $options['length'] : 0;
+ $func = $options['persistent'] ? 'pconnect' : 'connect';
+ $this->handler = new \Memcache; false === $options['timeout'] ?
+ $this->handler->$func($options['host'], $options['port']) :
+ $this->handler->$func($options['host'], $options['port'], $options['timeout']);
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return mixed
+ */
+ public function get($name)
+ {
+ N('cache_read', 1);
+ return $this->handler->get($this->options['prefix'] . $name);
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer $expire 有效时间(秒)
+ * @return boolean
+ */
+ public function set($name, $value, $expire = null)
+ {
+ N('cache_write', 1);
+ if (is_null($expire)) {
+ $expire = $this->options['expire'];
+ }
+ $name = $this->options['prefix'] . $name;
+ if ($this->handler->set($name, $value, 0, $expire)) {
+ if ($this->options['length'] > 0) {
+ // 记录缓存队列
+ $this->queue($name);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return boolean
+ */
+ public function rm($name, $ttl = false)
+ {
+ $name = $this->options['prefix'] . $name;
+ return false === $ttl ?
+ $this->handler->delete($name) :
+ $this->handler->delete($name, $ttl);
+ }
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return boolean
+ */
+ public function clear()
+ {
+ return $this->handler->flush();
+ }
+}
diff --git a/Framework/Library/Think/Cache/Driver/Memcached.class.php b/Framework/Library/Think/Cache/Driver/Memcached.class.php
new file mode 100644
index 00000000..82b38a5d
--- /dev/null
+++ b/Framework/Library/Think/Cache/Driver/Memcached.class.php
@@ -0,0 +1,109 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think\Cache\Driver;
+
+use Memcached as MemcachedResource;
+use Think\Cache;
+
+/**
+ * Memcached缓存驱动
+ */
+class Memcached extends Cache
+{
+
+ /**
+ *
+ * @param array $options
+ */
+ public function __construct($options = array())
+ {
+ if (!extension_loaded('memcached')) {
+ E(L('_NOT_SUPPORT_') . ':memcached');
+ }
+
+ $options = array_merge(array(
+ 'servers' => C('MEMCACHED_SERVER') ?: null,
+ 'lib_options' => C('MEMCACHED_LIB') ?: null,
+ ), $options);
+
+ $this->options = $options;
+ $this->options['expire'] = isset($options['expire']) ? $options['expire'] : C('DATA_CACHE_TIME');
+ $this->options['prefix'] = isset($options['prefix']) ? $options['prefix'] : C('DATA_CACHE_PREFIX');
+ $this->options['length'] = isset($options['length']) ? $options['length'] : 0;
+
+ $this->handler = new MemcachedResource;
+ $options['servers'] && $this->handler->addServers($options['servers']);
+ $options['lib_options'] && $this->handler->setOptions($options['lib_options']);
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return mixed
+ */
+ public function get($name)
+ {
+ N('cache_read', 1);
+ return $this->handler->get($this->options['prefix'] . $name);
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer $expire 有效时间(秒)
+ * @return boolean
+ */
+ public function set($name, $value, $expire = null)
+ {
+ N('cache_write', 1);
+ if (is_null($expire)) {
+ $expire = $this->options['expire'];
+ }
+ $name = $this->options['prefix'] . $name;
+ $expire = $expire == 0 ? 0 : time() + $expire;
+ if ($this->handler->set($name, $value, $expire)) {
+ if ($this->options['length'] > 0) {
+ // 记录缓存队列
+ $this->queue($name);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return boolean
+ */
+ public function rm($name, $ttl = false)
+ {
+ $name = $this->options['prefix'] . $name;
+ return false === $ttl ?
+ $this->handler->delete($name) :
+ $this->handler->delete($name, $ttl);
+ }
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return boolean
+ */
+ public function clear()
+ {
+ return $this->handler->flush();
+ }
+}
diff --git a/Framework/Library/Think/Cache/Driver/Memcachesae.class.php b/Framework/Library/Think/Cache/Driver/Memcachesae.class.php
new file mode 100644
index 00000000..576e5822
--- /dev/null
+++ b/Framework/Library/Think/Cache/Driver/Memcachesae.class.php
@@ -0,0 +1,157 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Cache\Driver;
+
+use Think\Cache;
+
+/**
+ * Memcache缓存驱动
+ * @category Extend
+ * @package Extend
+ * @subpackage Driver.Cache
+ * @author liu21st
+ */
+class Memcachesae extends Cache
+{
+
+ /**
+ * 架构函数
+ * @param array $options 缓存参数
+ * @access public
+ */
+ public function __construct($options = array())
+ {
+ $options = array_merge(array(
+ 'host' => C('MEMCACHE_HOST') ?: '127.0.0.1',
+ 'port' => C('MEMCACHE_PORT') ?: 11211,
+ 'timeout' => C('DATA_CACHE_TIMEOUT') ?: false,
+ 'persistent' => false,
+ ), $options);
+
+ $this->options = $options;
+ $this->options['expire'] = isset($options['expire']) ? $options['expire'] : C('DATA_CACHE_TIME');
+ $this->options['prefix'] = isset($options['prefix']) ? $options['prefix'] : C('DATA_CACHE_PREFIX');
+ $this->options['length'] = isset($options['length']) ? $options['length'] : 0;
+ $this->handler = memcache_init(); //[sae] 下实例化
+ //[sae] 下不用链接
+ $this->connected = true;
+ }
+
+ /**
+ * 是否连接
+ * @access private
+ * @return boolean
+ */
+ private function isConnected()
+ {
+ return $this->connected;
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return mixed
+ */
+ public function get($name)
+ {
+ N('cache_read', 1);
+ return $this->handler->get($_SERVER['HTTP_APPVERSION'] . '/' . $this->options['prefix'] . $name);
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer $expire 有效时间(秒)
+ * @return boolean
+ */
+ public function set($name, $value, $expire = null)
+ {
+ N('cache_write', 1);
+ if (is_null($expire)) {
+ $expire = $this->options['expire'];
+ }
+ $name = $this->options['prefix'] . $name;
+ if ($this->handler->set($_SERVER['HTTP_APPVERSION'] . '/' . $name, $value, 0, $expire)) {
+ if ($this->options['length'] > 0) {
+ // 记录缓存队列
+ $this->queue($name);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return boolean
+ */
+ public function rm($name, $ttl = false)
+ {
+ $name = $_SERVER['HTTP_APPVERSION'] . '/' . $this->options['prefix'] . $name;
+ return false === $ttl ?
+ $this->handler->delete($name) :
+ $this->handler->delete($name, $ttl);
+ }
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return boolean
+ */
+ public function clear()
+ {
+ return $this->handler->flush();
+ }
+
+ /**
+ * 队列缓存
+ * @access protected
+ * @param string $key 队列名
+ * @return mixed
+ */
+ //[sae] 下重写queque队列缓存方法
+ protected function queue($key)
+ {
+ $queue_name = isset($this->options['queue_name']) ? $this->options['queue_name'] : 'think_queue';
+ $value = F($queue_name);
+ if (!$value) {
+ $value = array();
+ }
+ // 进列
+ if (false === array_search($key, $value)) {
+ array_push($value, $key);
+ }
+
+ if (count($value) > $this->options['length']) {
+ // 出列
+ $key = array_shift($value);
+ // 删除缓存
+ $this->rm($key);
+ if (APP_DEBUG) {
+ //调试模式下记录出队次数
+ $counter = Think::instance('SaeCounter');
+ if ($counter->exists($queue_name . '_out_times')) {
+ $counter->incr($queue_name . '_out_times');
+ } else {
+ $counter->create($queue_name . '_out_times', 1);
+ }
+
+ }
+ }
+ return F($queue_name, $value);
+ }
+
+}
diff --git a/Framework/Library/Think/Cache/Driver/Redis.class.php b/Framework/Library/Think/Cache/Driver/Redis.class.php
new file mode 100644
index 00000000..b0bb97f0
--- /dev/null
+++ b/Framework/Library/Think/Cache/Driver/Redis.class.php
@@ -0,0 +1,117 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Cache\Driver;
+
+use Think\Cache;
+
+/**
+ * Redis缓存驱动
+ * 要求安装phpredis扩展:https://github.com/nicolasff/phpredis
+ */
+class Redis extends Cache
+{
+ /**
+ * 架构函数
+ * @param array $options 缓存参数
+ * @access public
+ */
+ public function __construct($options = array())
+ {
+ if (!extension_loaded('redis')) {
+ E(L('_NOT_SUPPORT_') . ':redis');
+ }
+ $options = array_merge(array(
+ 'host' => C('REDIS_HOST') ?: '127.0.0.1',
+ 'port' => C('REDIS_PORT') ?: 6379,
+ 'password' => C('REDIS_PASSWORD') ?: '',
+ 'timeout' => C('DATA_CACHE_TIMEOUT') ?: false,
+ 'persistent' => false,
+ ), $options);
+
+ $this->options = $options;
+ $this->options['expire'] = isset($options['expire']) ? $options['expire'] : C('DATA_CACHE_TIME');
+ $this->options['prefix'] = isset($options['prefix']) ? $options['prefix'] : C('DATA_CACHE_PREFIX');
+ $this->options['length'] = isset($options['length']) ? $options['length'] : 0;
+ $func = $options['persistent'] ? 'pconnect' : 'connect';
+ $this->handler = new \Redis;
+ false === $options['timeout'] ?
+ $this->handler->$func($options['host'], $options['port']) :
+ $this->handler->$func($options['host'], $options['port'], $options['timeout']);
+ if ('' != $options['password']) {
+ $this->handler->auth($options['password']);
+ }
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return mixed
+ */
+ public function get($name)
+ {
+ N('cache_read', 1);
+ $value = $this->handler->get($this->options['prefix'] . $name);
+ $jsonData = json_decode($value, true);
+ return (null === $jsonData) ? $value : $jsonData; //检测是否为JSON数据 true 返回JSON解析数组, false返回源数据
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer $expire 有效时间(秒)
+ * @return boolean
+ */
+ public function set($name, $value, $expire = null)
+ {
+ N('cache_write', 1);
+ if (is_null($expire)) {
+ $expire = $this->options['expire'];
+ }
+ $name = $this->options['prefix'] . $name;
+ //对数组/对象数据进行缓存处理,保证数据完整性
+ $value = (is_object($value) || is_array($value)) ? json_encode($value) : $value;
+ if (is_int($expire) && $expire) {
+ $result = $this->handler->setex($name, $expire, $value);
+ } else {
+ $result = $this->handler->set($name, $value);
+ }
+ if ($result && $this->options['length'] > 0) {
+ // 记录缓存队列
+ $this->queue($name);
+ }
+ return $result;
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return boolean
+ */
+ public function rm($name)
+ {
+ return $this->handler->delete($this->options['prefix'] . $name);
+ }
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return boolean
+ */
+ public function clear()
+ {
+ return $this->handler->flushDB();
+ }
+
+}
diff --git a/Framework/Library/Think/Cache/Driver/Shmop.class.php b/Framework/Library/Think/Cache/Driver/Shmop.class.php
new file mode 100644
index 00000000..be0c0261
--- /dev/null
+++ b/Framework/Library/Think/Cache/Driver/Shmop.class.php
@@ -0,0 +1,205 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Cache\Driver;
+
+use Think\Cache;
+
+/**
+ * Shmop缓存驱动
+ */
+class Shmop extends Cache
+{
+
+ /**
+ * 架构函数
+ * @param array $options 缓存参数
+ * @access public
+ */
+ public function __construct($options = array())
+ {
+ if (!extension_loaded('shmop')) {
+ E(L('_NOT_SUPPORT_') . ':shmop');
+ }
+ if (!empty($options)) {
+ $options = array(
+ 'size' => C('SHARE_MEM_SIZE'),
+ 'temp' => TEMP_PATH,
+ 'project' => 's',
+ 'length' => 0,
+ );
+ }
+ $this->options = $options;
+ $this->options['prefix'] = isset($options['prefix']) ? $options['prefix'] : C('DATA_CACHE_PREFIX');
+ $this->options['length'] = isset($options['length']) ? $options['length'] : 0;
+ $this->handler = $this->_ftok($this->options['project']);
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return mixed
+ */
+ public function get($name = false)
+ {
+ N('cache_read', 1);
+ $id = shmop_open($this->handler, 'c', 0600, 0);
+ if (false !== $id) {
+ $ret = unserialize(shmop_read($id, 0, shmop_size($id)));
+ shmop_close($id);
+
+ if (false === $name) {
+ return $ret;
+ }
+ $name = $this->options['prefix'] . $name;
+ if (isset($ret[$name])) {
+ $content = $ret[$name];
+ if (C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) {
+ //启用数据压缩
+ $content = gzuncompress($content);
+ }
+ return $content;
+ } else {
+ return null;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @return boolean
+ */
+ public function set($name, $value)
+ {
+ N('cache_write', 1);
+ $lh = $this->_lock();
+ $val = $this->get();
+ if (!is_array($val)) {
+ $val = array();
+ }
+
+ if (C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) {
+ //数据压缩
+ $value = gzcompress($value, 3);
+ }
+ $name = $this->options['prefix'] . $name;
+ $val[$name] = $value;
+ $val = serialize($val);
+ if ($this->_write($val, $lh)) {
+ if ($this->options['length'] > 0) {
+ // 记录缓存队列
+ $this->queue($name);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return boolean
+ */
+ public function rm($name)
+ {
+ $lh = $this->_lock();
+ $val = $this->get();
+ if (!is_array($val)) {
+ $val = array();
+ }
+
+ $name = $this->options['prefix'] . $name;
+ unset($val[$name]);
+ $val = serialize($val);
+ return $this->_write($val, $lh);
+ }
+
+ /**
+ * 生成IPC key
+ * @access private
+ * @param string $project 项目标识名
+ * @return integer
+ */
+ private function _ftok($project)
+ {
+ if (function_exists('ftok')) {
+ return ftok(__FILE__, $project);
+ }
+
+ if (strtoupper(PHP_OS) == 'WINNT') {
+ $s = stat(__FILE__);
+ return sprintf("%u", (($s['ino'] & 0xffff) | (($s['dev'] & 0xff) << 16) |
+ (($project & 0xff) << 24)));
+ } else {
+ $filename = __FILE__ . (string) $project;
+ for ($key = array(); sizeof($key) < strlen($filename); $key[] = ord(substr($filename, sizeof($key), 1)));
+ return dechex(array_sum($key));
+ }
+ }
+
+ /**
+ * 写入操作
+ * @access private
+ * @param string $name 缓存变量名
+ * @return integer|boolean
+ */
+ private function _write(&$val, &$lh)
+ {
+ $id = shmop_open($this->handler, 'c', 0600, $this->options['size']);
+ if ($id) {
+ $ret = shmop_write($id, $val, 0) == strlen($val);
+ shmop_close($id);
+ $this->_unlock($lh);
+ return $ret;
+ }
+ $this->_unlock($lh);
+ return false;
+ }
+
+ /**
+ * 共享锁定
+ * @access private
+ * @param string $name 缓存变量名
+ * @return boolean
+ */
+ private function _lock()
+ {
+ if (function_exists('sem_get')) {
+ $fp = sem_get($this->handler, 1, 0600, 1);
+ sem_acquire($fp);
+ } else {
+ $fp = fopen($this->options['temp'] . $this->options['prefix'] . md5($this->handler), 'w');
+ flock($fp, LOCK_EX);
+ }
+ return $fp;
+ }
+
+ /**
+ * 解除共享锁定
+ * @access private
+ * @param string $name 缓存变量名
+ * @return boolean
+ */
+ private function _unlock(&$fp)
+ {
+ if (function_exists('sem_release')) {
+ sem_release($fp);
+ } else {
+ fclose($fp);
+ }
+ }
+}
diff --git a/Framework/Library/Think/Cache/Driver/Sqlite.class.php b/Framework/Library/Think/Cache/Driver/Sqlite.class.php
new file mode 100644
index 00000000..2b89deb5
--- /dev/null
+++ b/Framework/Library/Think/Cache/Driver/Sqlite.class.php
@@ -0,0 +1,126 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Cache\Driver;
+
+use Think\Cache;
+
+/**
+ * Sqlite缓存驱动
+ */
+class Sqlite extends Cache
+{
+
+ /**
+ * 架构函数
+ * @param array $options 缓存参数
+ * @access public
+ */
+ public function __construct($options = array())
+ {
+ if (!extension_loaded('sqlite')) {
+ E(L('_NOT_SUPPORT_') . ':sqlite');
+ }
+ if (empty($options)) {
+ $options = array(
+ 'db' => ':memory:',
+ 'table' => 'sharedmemory',
+ );
+ }
+ $this->options = $options;
+ $this->options['prefix'] = isset($options['prefix']) ? $options['prefix'] : C('DATA_CACHE_PREFIX');
+ $this->options['length'] = isset($options['length']) ? $options['length'] : 0;
+ $this->options['expire'] = isset($options['expire']) ? $options['expire'] : C('DATA_CACHE_TIME');
+
+ $func = $this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open';
+ $this->handler = $func($this->options['db']);
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return mixed
+ */
+ public function get($name)
+ {
+ N('cache_read', 1);
+ $name = $this->options['prefix'] . sqlite_escape_string($name);
+ $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . time() . ') LIMIT 1';
+ $result = sqlite_query($this->handler, $sql);
+ if (sqlite_num_rows($result)) {
+ $content = sqlite_fetch_single($result);
+ if (C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) {
+ //启用数据压缩
+ $content = gzuncompress($content);
+ }
+ return unserialize($content);
+ }
+ return false;
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer $expire 有效时间(秒)
+ * @return boolean
+ */
+ public function set($name, $value, $expire = null)
+ {
+ N('cache_write', 1);
+ $name = $this->options['prefix'] . sqlite_escape_string($name);
+ $value = sqlite_escape_string(serialize($value));
+ if (is_null($expire)) {
+ $expire = $this->options['expire'];
+ }
+ $expire = (0 == $expire) ? 0 : (time() + $expire); //缓存有效期为0表示永久缓存
+ if (C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) {
+ //数据压缩
+ $value = gzcompress($value, 3);
+ }
+ $sql = 'REPLACE INTO ' . $this->options['table'] . ' (var, value,expire) VALUES (\'' . $name . '\', \'' . $value . '\', \'' . $expire . '\')';
+ if (sqlite_query($this->handler, $sql)) {
+ if ($this->options['length'] > 0) {
+ // 记录缓存队列
+ $this->queue($name);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return boolean
+ */
+ public function rm($name)
+ {
+ $name = $this->options['prefix'] . sqlite_escape_string($name);
+ $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\'';
+ sqlite_query($this->handler, $sql);
+ return true;
+ }
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return boolean
+ */
+ public function clear()
+ {
+ $sql = 'DELETE FROM ' . $this->options['table'];
+ sqlite_query($this->handler, $sql);
+ return;
+ }
+}
diff --git a/Framework/Library/Think/Cache/Driver/Wincache.class.php b/Framework/Library/Think/Cache/Driver/Wincache.class.php
new file mode 100644
index 00000000..a21ddd40
--- /dev/null
+++ b/Framework/Library/Think/Cache/Driver/Wincache.class.php
@@ -0,0 +1,95 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Cache\Driver;
+
+use Think\Cache;
+
+/**
+ * Wincache缓存驱动
+ */
+class Wincache extends Cache
+{
+
+ /**
+ * 架构函数
+ * @param array $options 缓存参数
+ * @access public
+ */
+ public function __construct($options = array())
+ {
+ if (!function_exists('wincache_ucache_info')) {
+ E(L('_NOT_SUPPORT_') . ':WinCache');
+ }
+ $this->options['expire'] = isset($options['expire']) ? $options['expire'] : C('DATA_CACHE_TIME');
+ $this->options['prefix'] = isset($options['prefix']) ? $options['prefix'] : C('DATA_CACHE_PREFIX');
+ $this->options['length'] = isset($options['length']) ? $options['length'] : 0;
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return mixed
+ */
+ public function get($name)
+ {
+ N('cache_read', 1);
+ $name = $this->options['prefix'] . $name;
+ return wincache_ucache_exists($name) ? wincache_ucache_get($name) : false;
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer $expire 有效时间(秒)
+ * @return boolean
+ */
+ public function set($name, $value, $expire = null)
+ {
+ N('cache_write', 1);
+ if (is_null($expire)) {
+ $expire = $this->options['expire'];
+ }
+ $name = $this->options['prefix'] . $name;
+ if (wincache_ucache_set($name, $value, $expire)) {
+ if ($this->options['length'] > 0) {
+ // 记录缓存队列
+ $this->queue($name);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return boolean
+ */
+ public function rm($name)
+ {
+ return wincache_ucache_delete($this->options['prefix'] . $name);
+ }
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return boolean
+ */
+ public function clear()
+ {
+ return wincache_ucache_clear();
+ }
+
+}
diff --git a/Framework/Library/Think/Cache/Driver/Xcache.class.php b/Framework/Library/Think/Cache/Driver/Xcache.class.php
new file mode 100644
index 00000000..151f6a76
--- /dev/null
+++ b/Framework/Library/Think/Cache/Driver/Xcache.class.php
@@ -0,0 +1,97 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Cache\Driver;
+
+use Think\Cache;
+
+/**
+ * Xcache缓存驱动
+ */
+class Xcache extends Cache
+{
+
+ /**
+ * 架构函数
+ * @param array $options 缓存参数
+ * @access public
+ */
+ public function __construct($options = array())
+ {
+ if (!function_exists('xcache_info')) {
+ E(L('_NOT_SUPPORT_') . ':Xcache');
+ }
+ $this->options['expire'] = isset($options['expire']) ? $options['expire'] : C('DATA_CACHE_TIME');
+ $this->options['prefix'] = isset($options['prefix']) ? $options['prefix'] : C('DATA_CACHE_PREFIX');
+ $this->options['length'] = isset($options['length']) ? $options['length'] : 0;
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return mixed
+ */
+ public function get($name)
+ {
+ N('cache_read', 1);
+ $name = $this->options['prefix'] . $name;
+ if (xcache_isset($name)) {
+ return xcache_get($name);
+ }
+ return false;
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer $expire 有效时间(秒)
+ * @return boolean
+ */
+ public function set($name, $value, $expire = null)
+ {
+ N('cache_write', 1);
+ if (is_null($expire)) {
+ $expire = $this->options['expire'];
+ }
+ $name = $this->options['prefix'] . $name;
+ if (xcache_set($name, $value, $expire)) {
+ if ($this->options['length'] > 0) {
+ // 记录缓存队列
+ $this->queue($name);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return boolean
+ */
+ public function rm($name)
+ {
+ return xcache_unset($this->options['prefix'] . $name);
+ }
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return boolean
+ */
+ public function clear()
+ {
+ return xcache_clear_cache(1, -1);
+ }
+}
diff --git a/Framework/Library/Think/Controller.class.php b/Framework/Library/Think/Controller.class.php
new file mode 100644
index 00000000..aa1149b8
--- /dev/null
+++ b/Framework/Library/Think/Controller.class.php
@@ -0,0 +1,354 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * ThinkPHP 控制器基类 抽象类
+ */
+abstract class Controller
+{
+ /**
+ * 视图实例对象
+ * @var view
+ * @access protected
+ */
+ protected $view = null;
+
+ /**
+ * 控制器参数
+ * @var config
+ * @access protected
+ */
+ protected $config = array();
+
+ /**
+ * 架构函数 取得模板对象实例
+ * @access public
+ */
+ public function __construct()
+ {
+ Hook::listen('action_begin', $this->config);
+ //实例化视图类
+ $this->view = Think::instance('Think\View');
+ //控制器初始化
+ if (method_exists($this, '_initialize')) {
+ $this->_initialize();
+ }
+
+ }
+
+ /**
+ * 模板显示 调用内置的模板引擎显示方法,
+ * @access protected
+ * @param string $templateFile 指定要调用的模板文件
+ * 默认为空 由系统自动定位模板文件
+ * @param string $charset 输出编码
+ * @param string $contentType 输出类型
+ * @param string $content 输出内容
+ * @param string $prefix 模板缓存前缀
+ * @return void
+ */
+ protected function display($templateFile = '', $charset = '', $contentType = '', $content = '', $prefix = '')
+ {
+ $this->view->display($templateFile, $charset, $contentType, $content, $prefix);
+ }
+
+ /**
+ * 输出内容文本可以包括Html 并支持内容解析
+ * @access protected
+ * @param string $content 输出内容
+ * @param string $charset 模板输出字符集
+ * @param string $contentType 输出类型
+ * @param string $prefix 模板缓存前缀
+ * @return mixed
+ */
+ protected function show($content, $charset = '', $contentType = '', $prefix = '')
+ {
+ $this->view->display('', $charset, $contentType, $content, $prefix);
+ }
+
+ /**
+ * 获取输出页面内容
+ * 调用内置的模板引擎fetch方法,
+ * @access protected
+ * @param string $templateFile 指定要调用的模板文件
+ * 默认为空 由系统自动定位模板文件
+ * @param string $content 模板输出内容
+ * @param string $prefix 模板缓存前缀*
+ * @return string
+ */
+ protected function fetch($templateFile = '', $content = '', $prefix = '')
+ {
+ return $this->view->fetch($templateFile, $content, $prefix);
+ }
+
+ /**
+ * 创建静态页面
+ * @access protected
+ * @htmlfile 生成的静态文件名称
+ * @htmlpath 生成的静态文件路径
+ * @param string $templateFile 指定要调用的模板文件
+ * 默认为空 由系统自动定位模板文件
+ * @return string
+ */
+ protected function buildHtml($htmlfile = '', $htmlpath = '', $templateFile = '')
+ {
+ $content = $this->fetch($templateFile);
+ $htmlpath = !empty($htmlpath) ? $htmlpath : HTML_PATH;
+ $htmlfile = $htmlpath . $htmlfile . C('HTML_FILE_SUFFIX');
+ Storage::put($htmlfile, $content, 'html');
+ return $content;
+ }
+
+ /**
+ * 模板主题设置
+ * @access protected
+ * @param string $theme 模版主题
+ * @return Action
+ */
+ protected function theme($theme)
+ {
+ $this->view->theme($theme);
+ return $this;
+ }
+
+ /**
+ * 模板变量赋值
+ * @access protected
+ * @param mixed $name 要显示的模板变量
+ * @param mixed $value 变量的值
+ * @return Action
+ */
+ protected function assign($name, $value = '')
+ {
+ $this->view->assign($name, $value);
+ return $this;
+ }
+
+ public function __set($name, $value)
+ {
+ $this->assign($name, $value);
+ }
+
+ /**
+ * 取得模板显示变量的值
+ * @access protected
+ * @param string $name 模板显示变量
+ * @return mixed
+ */
+ public function get($name = '')
+ {
+ return $this->view->get($name);
+ }
+
+ public function __get($name)
+ {
+ return $this->get($name);
+ }
+
+ /**
+ * 检测模板变量的值
+ * @access public
+ * @param string $name 名称
+ * @return boolean
+ */
+ public function __isset($name)
+ {
+ return $this->get($name);
+ }
+
+ /**
+ * 魔术方法 有不存在的操作的时候执行
+ * @access public
+ * @param string $method 方法名
+ * @param array $args 参数
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ if (0 === strcasecmp($method, ACTION_NAME . C('ACTION_SUFFIX'))) {
+ if (method_exists($this, '_empty')) {
+ // 如果定义了_empty操作 则调用
+ $this->_empty($method, $args);
+ } elseif (file_exists_case($this->view->parseTemplate())) {
+ // 检查是否存在默认模版 如果有直接输出模版
+ $this->display();
+ } else {
+ E(L('_ERROR_ACTION_') . ':' . ACTION_NAME);
+ }
+ } else {
+ E(__CLASS__ . ':' . $method . L('_METHOD_NOT_EXIST_'));
+ return;
+ }
+ }
+
+ /**
+ * 操作错误跳转的快捷方法
+ * @access protected
+ * @param string $message 错误信息
+ * @param string $jumpUrl 页面跳转地址
+ * @param mixed $ajax 是否为Ajax方式 当数字时指定跳转时间
+ * @return void
+ */
+ protected function error($message = '', $jumpUrl = '', $ajax = false)
+ {
+ $this->dispatchJump($message, 0, $jumpUrl, $ajax);
+ }
+
+ /**
+ * 操作成功跳转的快捷方法
+ * @access protected
+ * @param string $message 提示信息
+ * @param string $jumpUrl 页面跳转地址
+ * @param mixed $ajax 是否为Ajax方式 当数字时指定跳转时间
+ * @return void
+ */
+ protected function success($message = '', $jumpUrl = '', $ajax = false)
+ {
+ $this->dispatchJump($message, 1, $jumpUrl, $ajax);
+ }
+
+ /**
+ * Ajax方式返回数据到客户端
+ * @access protected
+ * @param mixed $data 要返回的数据
+ * @param String $type AJAX返回数据格式
+ * @param int $json_option 传递给json_encode的option参数
+ * @return void
+ */
+ protected function ajaxReturn($data, $type = '', $json_option = 0)
+ {
+ if (empty($type)) {
+ $type = C('DEFAULT_AJAX_RETURN');
+ }
+
+ switch (strtoupper($type)) {
+ case 'JSON':
+ // 返回JSON数据格式到客户端 包含状态信息
+ header('Content-Type:application/json; charset=utf-8');
+ exit(json_encode($data, $json_option));
+ case 'XML':
+ // 返回xml格式数据
+ header('Content-Type:text/xml; charset=utf-8');
+ exit(xml_encode($data));
+ case 'JSONP':
+ // 返回JSON数据格式到客户端 包含状态信息
+ header('Content-Type:application/json; charset=utf-8');
+ $handler = isset($_GET[C('VAR_JSONP_HANDLER')]) ? $_GET[C('VAR_JSONP_HANDLER')] : C('DEFAULT_JSONP_HANDLER');
+ exit($handler . '(' . json_encode($data, $json_option) . ');');
+ case 'EVAL':
+ // 返回可执行的js脚本
+ header('Content-Type:text/html; charset=utf-8');
+ exit($data);
+ default:
+ // 用于扩展其他返回格式数据
+ Hook::listen('ajax_return', $data);
+ }
+ }
+
+ /**
+ * Action跳转(URL重定向) 支持指定模块和延时跳转
+ * @access protected
+ * @param string $url 跳转的URL表达式
+ * @param array $params 其它URL参数
+ * @param integer $delay 延时跳转的时间 单位为秒
+ * @param string $msg 跳转提示信息
+ * @return void
+ */
+ protected function redirect($url, $params = array(), $delay = 0, $msg = '')
+ {
+ $url = U($url, $params);
+ redirect($url, $delay, $msg);
+ }
+
+ /**
+ * 默认跳转操作 支持错误导向和正确跳转
+ * 调用模板显示 默认为public目录下面的success页面
+ * 提示页面为可配置 支持模板标签
+ * @param string $message 提示信息
+ * @param Boolean $status 状态
+ * @param string $jumpUrl 页面跳转地址
+ * @param mixed $ajax 是否为Ajax方式 当数字时指定跳转时间
+ * @access private
+ * @return void
+ */
+ private function dispatchJump($message, $status = 1, $jumpUrl = '', $ajax = false)
+ {
+ if (true === $ajax || IS_AJAX) {
+ // AJAX提交
+ $data = is_array($ajax) ? $ajax : array();
+ $data['info'] = $message;
+ $data['status'] = $status;
+ $data['url'] = $jumpUrl;
+ $this->ajaxReturn($data);
+ }
+ if (is_int($ajax)) {
+ $this->assign('waitSecond', $ajax);
+ }
+
+ if (!empty($jumpUrl)) {
+ $this->assign('jumpUrl', $jumpUrl);
+ }
+
+ // 提示标题
+ $this->assign('msgTitle', $status ? L('_OPERATION_SUCCESS_') : L('_OPERATION_FAIL_'));
+ //如果设置了关闭窗口,则提示完毕后自动关闭窗口
+ if ($this->get('closeWin')) {
+ $this->assign('jumpUrl', 'javascript:window.close();');
+ }
+
+ $this->assign('status', $status); // 状态
+ //保证输出不受静态缓存影响
+ C('HTML_CACHE_ON', false);
+ if ($status) {
+ //发送成功信息
+ $this->assign('message', $message); // 提示信息
+ // 成功操作后默认停留1秒
+ if (!isset($this->waitSecond)) {
+ $this->assign('waitSecond', '1');
+ }
+
+ // 默认操作成功自动返回操作前页面
+ if (!isset($this->jumpUrl)) {
+ $this->assign("jumpUrl", $_SERVER["HTTP_REFERER"]);
+ }
+
+ $this->display(C('TMPL_ACTION_SUCCESS'));
+ } else {
+ $this->assign('error', $message); // 提示信息
+ //发生错误时候默认停留3秒
+ if (!isset($this->waitSecond)) {
+ $this->assign('waitSecond', '3');
+ }
+
+ // 默认发生错误的话自动返回上页
+ if (!isset($this->jumpUrl)) {
+ $this->assign('jumpUrl', "javascript:history.back(-1);");
+ }
+
+ $this->display(C('TMPL_ACTION_ERROR'));
+ // 中止执行 避免出错后继续执行
+ exit;
+ }
+ }
+
+ /**
+ * 析构方法
+ * @access public
+ */
+ public function __destruct()
+ {
+ // 执行后续操作
+ Hook::listen('action_end');
+ }
+}
+// 设置控制器别名 便于升级
+class_alias('Think\Controller', 'Think\Action');
diff --git a/Framework/Library/Think/Controller/HproseController.class.php b/Framework/Library/Think/Controller/HproseController.class.php
new file mode 100644
index 00000000..3ce92d6c
--- /dev/null
+++ b/Framework/Library/Think/Controller/HproseController.class.php
@@ -0,0 +1,67 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Controller;
+
+/**
+ * ThinkPHP Hprose控制器类
+ */
+class HproseController
+{
+
+ protected $allowMethodList = '';
+ protected $crossDomain = false;
+ protected $P3P = false;
+ protected $get = true;
+ protected $debug = false;
+
+ /**
+ * 架构函数
+ * @access public
+ */
+ public function __construct()
+ {
+ //控制器初始化
+ if (method_exists($this, '_initialize')) {
+ $this->_initialize();
+ }
+
+ //导入类库
+ Vendor('Hprose.HproseHttpServer');
+ //实例化HproseHttpServer
+ $server = new \HproseHttpServer();
+ if ($this->allowMethodList) {
+ $methods = $this->allowMethodList;
+ } else {
+ $methods = get_class_methods($this);
+ $methods = array_diff($methods, array('__construct', '__call', '_initialize'));
+ }
+ $server->addMethods($methods, $this);
+ if (APP_DEBUG || $this->debug) {
+ $server->setDebugEnabled(true);
+ }
+ // Hprose设置
+ $server->setCrossDomainEnabled($this->crossDomain);
+ $server->setP3PEnabled($this->P3P);
+ $server->setGetEnabled($this->get);
+ // 启动server
+ $server->start();
+ }
+
+ /**
+ * 魔术方法 有不存在的操作的时候执行
+ * @access public
+ * @param string $method 方法名
+ * @param array $args 参数
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {}
+}
diff --git a/Framework/Library/Think/Controller/JsonRpcController.class.php b/Framework/Library/Think/Controller/JsonRpcController.class.php
new file mode 100644
index 00000000..b34b9807
--- /dev/null
+++ b/Framework/Library/Think/Controller/JsonRpcController.class.php
@@ -0,0 +1,46 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Controller;
+
+/**
+ * ThinkPHP JsonRPC控制器类
+ */
+use jsonRPCServer as jsonRPCServer;
+class JsonRpcController
+{
+
+ /**
+ * 架构函数
+ * @access public
+ */
+ public function __construct()
+ {
+ //控制器初始化
+ if (method_exists($this, '_initialize')) {
+ $this->_initialize();
+ }
+
+ //导入类库
+ Vendor('jsonRPC.jsonRPCServer');
+ // 启动server
+ \jsonRPCServer::handle($this);
+ }
+
+ /**
+ * 魔术方法 有不存在的操作的时候执行
+ * @access public
+ * @param string $method 方法名
+ * @param array $args 参数
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {}
+}
diff --git a/Framework/Library/Think/Controller/RestController.class.php b/Framework/Library/Think/Controller/RestController.class.php
new file mode 100644
index 00000000..99298f2e
--- /dev/null
+++ b/Framework/Library/Think/Controller/RestController.class.php
@@ -0,0 +1,297 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Controller;
+
+use Think\App;
+use Think\Controller;
+
+/**
+ * ThinkPHP REST控制器类
+ */
+class RestController extends Controller
+{
+ // 当前请求类型
+ protected $_method = '';
+ // 当前请求的资源类型
+ protected $_type = '';
+ // REST允许的请求类型列表
+ protected $allowMethod = array('get', 'post', 'put', 'delete');
+ // REST默认请求类型
+ protected $defaultMethod = 'get';
+ // REST允许请求的资源类型列表
+ protected $allowType = array('html', 'xml', 'json', 'rss');
+ // 默认的资源类型
+ protected $defaultType = 'html';
+ // REST允许输出的资源类型列表
+ protected $allowOutputType = array(
+ 'xml' => 'application/xml',
+ 'json' => 'application/json',
+ 'html' => 'text/html',
+ );
+
+ /**
+ * 架构函数
+ * @access public
+ */
+ public function __construct()
+ {
+ // 资源类型检测
+ if ('' == __EXT__) {
+ // 自动检测资源类型
+ $this->_type = $this->getAcceptType();
+ } elseif (!in_array(__EXT__, $this->allowType)) {
+ // 资源类型非法 则用默认资源类型访问
+ $this->_type = $this->defaultType;
+ } else {
+ $this->_type = __EXT__;
+ }
+
+ // 请求方式检测
+ $method = strtolower(REQUEST_METHOD);
+ if (!in_array($method, $this->allowMethod)) {
+ // 请求方式非法 则用默认请求方法
+ $method = $this->defaultMethod;
+ }
+ $this->_method = $method;
+
+ parent::__construct();
+ }
+
+ /**
+ * 魔术方法 有不存在的操作的时候执行
+ * @access public
+ * @param string $method 方法名
+ * @param array $args 参数
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ if (0 === strcasecmp($method, ACTION_NAME . C('ACTION_SUFFIX'))) {
+ if (method_exists($this, $method . '_' . $this->_method . '_' . $this->_type)) {
+ // RESTFul方法支持
+ $fun = $method . '_' . $this->_method . '_' . $this->_type;
+ App::invokeAction($this, $fun);
+ } elseif ($this->_method == $this->defaultMethod && method_exists($this, $method . '_' . $this->_type)) {
+ $fun = $method . '_' . $this->_type;
+ App::invokeAction($this, $fun);
+ } elseif ($this->_type == $this->defaultType && method_exists($this, $method . '_' . $this->_method)) {
+ $fun = $method . '_' . $this->_method;
+ App::invokeAction($this, $fun);
+ } elseif (method_exists($this, '_empty')) {
+ // 如果定义了_empty操作 则调用
+ $this->_empty($method, $args);
+ } elseif (file_exists_case($this->view->parseTemplate())) {
+ // 检查是否存在默认模版 如果有直接输出模版
+ $this->display();
+ } else {
+ E(L('_ERROR_ACTION_') . ':' . ACTION_NAME);
+ }
+ }
+ }
+
+ /**
+ * 获取当前请求的Accept头信息
+ * @return string
+ */
+ protected function getAcceptType()
+ {
+ $type = array(
+ 'html' => 'text/html,application/xhtml+xml,*/*',
+ 'xml' => 'application/xml,text/xml,application/x-xml',
+ 'json' => 'application/json,text/x-json,application/jsonrequest,text/json',
+ 'js' => 'text/javascript,application/javascript,application/x-javascript',
+ 'css' => 'text/css',
+ 'rss' => 'application/rss+xml',
+ 'yaml' => 'application/x-yaml,text/yaml',
+ 'atom' => 'application/atom+xml',
+ 'pdf' => 'application/pdf',
+ 'text' => 'text/plain',
+ 'png' => 'image/png',
+ 'jpg' => 'image/jpg,image/jpeg,image/pjpeg',
+ 'gif' => 'image/gif',
+ 'csv' => 'text/csv',
+ );
+
+ foreach ($type as $key => $val) {
+ $array = explode(',', $val);
+ foreach ($array as $k => $v) {
+ if(array_key_exists('HTTP_ACCEPT',$_SERVER))
+ if (stristr($_SERVER['HTTP_ACCEPT'], $v)) {
+ return $key;
+ }
+ }
+ }
+ return false;
+ }
+
+ // 发送Http状态信息
+ protected function sendHttpStatus($code)
+ {
+ static $_status = array(
+ // Informational 1xx
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ // Success 2xx
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ // Redirection 3xx
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Moved Temporarily ', // 1.1
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ // 306 is deprecated but reserved
+ 307 => 'Temporary Redirect',
+ // Client Error 4xx
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ // Server Error 5xx
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ 509 => 'Bandwidth Limit Exceeded',
+ );
+ if (isset($_status[$code])) {
+ header('HTTP/1.1 ' . $code . ' ' . $_status[$code]);
+ // 确保FastCGI模式下正常
+ header('Status:' . $code . ' ' . $_status[$code]);
+ }
+ }
+
+ /**
+ * 编码数据
+ * @access protected
+ * @param mixed $data 要返回的数据
+ * @param String $type 返回类型 JSON XML
+ * @return string
+ */
+ protected function encodeData($data, $type = '')
+ {
+ if (empty($data)) {
+ return '';
+ }
+
+ if('json' == $type) {
+ // 返回JSON数据格式到客户端 包含状态信息
+ if(version_compare(PHP_VERSION,'5.4.0','<')) {
+ $this->arrayRecursive($data, 'urlencode', true);
+ $data = urldecode(json_encode($data));
+ } else {
+ $data = json_encode($data,JSON_UNESCAPED_UNICODE);
+ }
+ } elseif ('xml' == $type) {
+ // 返回xml格式数据
+ $data = xml_encode($data);
+ } elseif ('php' == $type) {
+ $data = serialize($data);
+ } // 默认直接输出
+ $this->setContentType($type);
+ //header('Content-Length: ' . strlen($data));
+ return $data;
+ }
+
+ /**************************************************************
+ *
+ * 使用特定function对数组中所有元素做处理
+ * @param string|array &$array 要处理的字符串或者数组
+ * @param string $function 要执行的函数
+ * @return boolean $apply_to_keys_also 是否也应用到key上
+ * @access protected
+ *
+ *************************************************************/
+ protected function arrayRecursive(&$array, $function, $apply_to_keys_also = false)
+ {
+ static $recursive_counter = 0;
+ if (++$recursive_counter > 1000) {
+ die('possible deep recursion attack');
+ }
+ foreach ($array as $key => $value) {
+ if (is_array($value)) {
+ $this->arrayRecursive($array[$key], $function, $apply_to_keys_also);
+ } elseif(is_string($value)) {
+ $array[$key] = $function($value);
+ }
+
+ if ($apply_to_keys_also && is_string($key)) {
+ $new_key = $function($key);
+ if ($new_key != $key) {
+ $array[$new_key] = $array[$key];
+ unset($array[$key]);
+ }
+ }
+ }
+ $recursive_counter--;
+ }
+
+ /**
+ * 设置页面输出的CONTENT_TYPE和编码
+ * @access public
+ * @param string $type content_type 类型对应的扩展名
+ * @param string $charset 页面输出编码
+ * @return void
+ */
+ public function setContentType($type, $charset = '')
+ {
+ if (headers_sent()) {
+ return;
+ }
+
+ if (empty($charset)) {
+ $charset = C('DEFAULT_CHARSET');
+ }
+
+ $type = strtolower($type);
+ if (isset($this->allowOutputType[$type])) //过滤content_type
+ {
+ header('Content-Type: ' . $this->allowOutputType[$type] . '; charset=' . $charset);
+ }
+
+ }
+
+ /**
+ * 输出返回数据
+ * @access protected
+ * @param mixed $data 要返回的数据
+ * @param String $type 返回类型 JSON XML
+ * @param integer $code HTTP状态
+ * @return void
+ */
+ protected function response($data, $type = '', $code = 200)
+ {
+ $this->sendHttpStatus($code);
+ exit($this->encodeData($data, strtolower($type)));
+ }
+}
diff --git a/Framework/Library/Think/Controller/RpcController.class.php b/Framework/Library/Think/Controller/RpcController.class.php
new file mode 100644
index 00000000..c2516720
--- /dev/null
+++ b/Framework/Library/Think/Controller/RpcController.class.php
@@ -0,0 +1,62 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Controller;
+
+/**
+ * ThinkPHP RPC控制器类
+ */
+class RpcController
+{
+
+ protected $allowMethodList = '';
+ protected $debug = false;
+
+ /**
+ * 架构函数
+ * @access public
+ */
+ public function __construct()
+ {
+ //控制器初始化
+ if (method_exists($this, '_initialize')) {
+ $this->_initialize();
+ }
+
+ //导入类库
+ Vendor('phpRPC.phprpc_server');
+ //实例化phprpc
+ $server = new \PHPRPC_Server();
+ if ($this->allowMethodList) {
+ $methods = $this->allowMethodList;
+ } else {
+ $methods = get_class_methods($this);
+ $methods = array_diff($methods, array('__construct', '__call', '_initialize'));
+ }
+ $server->add($methods, $this);
+
+ if (APP_DEBUG || $this->debug) {
+ $server->setDebugMode(true);
+ }
+ $server->setEnableGZIP(true);
+ $server->start();
+ echo $server->comment();
+ }
+
+ /**
+ * 魔术方法 有不存在的操作的时候执行
+ * @access public
+ * @param string $method 方法名
+ * @param array $args 参数
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {}
+}
diff --git a/Framework/Library/Think/Controller/YarController.class.php b/Framework/Library/Think/Controller/YarController.class.php
new file mode 100644
index 00000000..7860331d
--- /dev/null
+++ b/Framework/Library/Think/Controller/YarController.class.php
@@ -0,0 +1,50 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Controller;
+
+/**
+ * ThinkPHP Yar控制器类
+ */
+class YarController
+{
+
+ /**
+ * 架构函数
+ * @access public
+ */
+ public function __construct()
+ {
+ //控制器初始化
+ if (method_exists($this, '_initialize')) {
+ $this->_initialize();
+ }
+
+ //判断扩展是否存在
+ if (!extension_loaded('yar')) {
+ E(L('_NOT_SUPPORT_') . ':yar');
+ }
+
+ //实例化Yar_Server
+ $server = new \Yar_Server($this);
+ // 启动server
+ $server->handle();
+ }
+
+ /**
+ * 魔术方法 有不存在的操作的时候执行
+ * @access public
+ * @param string $method 方法名
+ * @param array $args 参数
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {}
+}
diff --git a/Framework/Library/Think/Crypt.class.php b/Framework/Library/Think/Crypt.class.php
new file mode 100644
index 00000000..6a81cdaa
--- /dev/null
+++ b/Framework/Library/Think/Crypt.class.php
@@ -0,0 +1,58 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * 加密解密类
+ */
+class Crypt
+{
+
+ private static $handler = '';
+
+ public static function init($type = '')
+ {
+ $type = $type ?: C('DATA_CRYPT_TYPE');
+ $class = strpos($type, '\\') ? $type : 'Think\\Crypt\\Driver\\' . ucwords(strtolower($type));
+ self::$handler = $class;
+ }
+
+ /**
+ * 加密字符串
+ * @param string $str 字符串
+ * @param string $key 加密key
+ * @param integer $expire 有效期(秒) 0 为永久有效
+ * @return string
+ */
+ public static function encrypt($data, $key, $expire = 0)
+ {
+ if (empty(self::$handler)) {
+ self::init();
+ }
+ $class = self::$handler;
+ return $class::encrypt($data, $key, $expire);
+ }
+
+ /**
+ * 解密字符串
+ * @param string $str 字符串
+ * @param string $key 加密key
+ * @return string
+ */
+ public static function decrypt($data, $key)
+ {
+ if (empty(self::$handler)) {
+ self::init();
+ }
+ $class = self::$handler;
+ return $class::decrypt($data, $key);
+ }
+}
diff --git a/Framework/Library/Think/Crypt/Driver/Base64.class.php b/Framework/Library/Think/Crypt/Driver/Base64.class.php
new file mode 100644
index 00000000..a54ff878
--- /dev/null
+++ b/Framework/Library/Think/Crypt/Driver/Base64.class.php
@@ -0,0 +1,84 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Crypt\Driver;
+
+/**
+ * Base64 加密实现类
+ */
+class Base64
+{
+
+ /**
+ * 加密字符串
+ * @param string $str 字符串
+ * @param string $key 加密key
+ * @param integer $expire 有效期(秒)
+ * @return string
+ */
+ public static function encrypt($data, $key, $expire = 0)
+ {
+ $expire = sprintf('%010d', $expire ? $expire + time() : 0);
+ $key = md5($key);
+ $data = base64_encode($expire . $data);
+ $x = 0;
+ $len = strlen($data);
+ $l = strlen($key);
+ for ($i = 0; $i < $len; $i++) {
+ if ($x == $l) {
+ $x = 0;
+ }
+
+ $char .= substr($key, $x, 1);
+ $x++;
+ }
+
+ for ($i = 0; $i < $len; $i++) {
+ $str .= chr(ord(substr($data, $i, 1)) + (ord(substr($char, $i, 1))) % 256);
+ }
+ return $str;
+ }
+
+ /**
+ * 解密字符串
+ * @param string $str 字符串
+ * @param string $key 加密key
+ * @return string
+ */
+ public static function decrypt($data, $key)
+ {
+ $key = md5($key);
+ $x = 0;
+ $len = strlen($data);
+ $l = strlen($key);
+ for ($i = 0; $i < $len; $i++) {
+ if ($x == $l) {
+ $x = 0;
+ }
+
+ $char .= substr($key, $x, 1);
+ $x++;
+ }
+ for ($i = 0; $i < $len; $i++) {
+ if (ord(substr($data, $i, 1)) < ord(substr($char, $i, 1))) {
+ $str .= chr((ord(substr($data, $i, 1)) + 256) - ord(substr($char, $i, 1)));
+ } else {
+ $str .= chr(ord(substr($data, $i, 1)) - ord(substr($char, $i, 1)));
+ }
+ }
+ $data = base64_decode($str);
+ $expire = substr($data, 0, 10);
+ if ($expire > 0 && $expire < time()) {
+ return '';
+ }
+ $data = substr($data, 10);
+ return $data;
+ }
+}
diff --git a/Framework/Library/Think/Crypt/Driver/Crypt.class.php b/Framework/Library/Think/Crypt/Driver/Crypt.class.php
new file mode 100644
index 00000000..cf693903
--- /dev/null
+++ b/Framework/Library/Think/Crypt/Driver/Crypt.class.php
@@ -0,0 +1,93 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Crypt\Driver;
+
+/**
+ * Crypt 加密实现类
+ * @category ORG
+ * @package ORG
+ * @subpackage Crypt
+ * @author liu21st
+ */
+class Crypt
+{
+
+ /**
+ * 加密字符串
+ * @param string $str 字符串
+ * @param string $key 加密key
+ * @param integer $expire 有效期(秒)
+ * @return string
+ */
+ public static function encrypt($str, $key, $expire = 0)
+ {
+ $expire = sprintf('%010d', $expire ? $expire + time() : 0);
+ $r = md5($key);
+ $c = 0;
+ $v = "";
+ $str = $expire . $str;
+ $len = strlen($str);
+ $l = strlen($r);
+ for ($i = 0; $i < $len; $i++) {
+ if ($c == $l) {
+ $c = 0;
+ }
+
+ $v .= substr($r, $c, 1) .
+ (substr($str, $i, 1) ^ substr($r, $c, 1));
+ $c++;
+ }
+ return self::ed($v, $key);
+ }
+
+ /**
+ * 解密字符串
+ * @param string $str 字符串
+ * @param string $key 加密key
+ * @return string
+ */
+ public static function decrypt($str, $key)
+ {
+ $str = self::ed($str, $key);
+ $v = "";
+ $len = strlen($str);
+ for ($i = 0; $i < $len; $i++) {
+ $md5 = substr($str, $i, 1);
+ $i++;
+ $v .= (substr($str, $i, 1) ^ $md5);
+ }
+ $data = $v;
+ $expire = substr($data, 0, 10);
+ if ($expire > 0 && $expire < time()) {
+ return '';
+ }
+ $data = substr($data, 10);
+ return $data;
+ }
+
+ private static function ed($str, $key)
+ {
+ $r = md5($key);
+ $c = 0;
+ $v = '';
+ $len = strlen($str);
+ $l = strlen($r);
+ for ($i = 0; $i < $len; $i++) {
+ if ($c == $l) {
+ $c = 0;
+ }
+
+ $v .= substr($str, $i, 1) ^ substr($r, $c, 1);
+ $c++;
+ }
+ return $v;
+ }
+}
diff --git a/Framework/Library/Think/Crypt/Driver/Des.class.php b/Framework/Library/Think/Crypt/Driver/Des.class.php
new file mode 100644
index 00000000..4deba6bb
--- /dev/null
+++ b/Framework/Library/Think/Crypt/Driver/Des.class.php
@@ -0,0 +1,299 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Crypt\Driver;
+
+/**
+ * Des 加密实现类
+ * Converted from JavaScript to PHP by Jim Gibbs, June 2004 Paul Tero, July 2001
+ * Optimised for performance with large blocks by Michael Hayworth, November 2001
+ * http://www.netdealing.com
+ */
+
+class Des
+{
+
+ /**
+ * 加密字符串
+ * @param string $str 字符串
+ * @param string $key 加密key
+ * @param integer $expire 有效期(秒)
+ * @return string
+ */
+ public static function encrypt($str, $key, $expire = 0)
+ {
+ if ("" == $str) {
+ return "";
+ }
+ $expire = sprintf('%010d', $expire ? $expire + time() : 0);
+ $str = $expire . $str;
+ return self::_des($key, $str, 1);
+ }
+
+ /**
+ * 解密字符串
+ * @param string $str 字符串
+ * @param string $key 加密key
+ * @return string
+ */
+ public static function decrypt($str, $key)
+ {
+ if ("" == $str) {
+ return "";
+ }
+ $data = self::_des($key, $str, 0);
+ $expire = substr($data, 0, 10);
+ if ($expire > 0 && $expire < time()) {
+ return '';
+ }
+ $data = substr($data, 10);
+ return $data;
+ }
+
+ /**
+ * Des算法
+ * @param string $str 字符串
+ * @param string $key 加密key
+ * @return string
+ */
+ private static function _des($key, $message, $encrypt, $mode = 0, $iv = null)
+ {
+ //declaring this locally speeds things up a bit
+ $spfunction1 = array(0x1010400, 0, 0x10000, 0x1010404, 0x1010004, 0x10404, 0x4, 0x10000, 0x400, 0x1010400, 0x1010404, 0x400, 0x1000404, 0x1010004, 0x1000000, 0x4, 0x404, 0x1000400, 0x1000400, 0x10400, 0x10400, 0x1010000, 0x1010000, 0x1000404, 0x10004, 0x1000004, 0x1000004, 0x10004, 0, 0x404, 0x10404, 0x1000000, 0x10000, 0x1010404, 0x4, 0x1010000, 0x1010400, 0x1000000, 0x1000000, 0x400, 0x1010004, 0x10000, 0x10400, 0x1000004, 0x400, 0x4, 0x1000404, 0x10404, 0x1010404, 0x10004, 0x1010000, 0x1000404, 0x1000004, 0x404, 0x10404, 0x1010400, 0x404, 0x1000400, 0x1000400, 0, 0x10004, 0x10400, 0, 0x1010004);
+ $spfunction2 = array(-0x7fef7fe0, -0x7fff8000, 0x8000, 0x108020, 0x100000, 0x20, -0x7fefffe0, -0x7fff7fe0, -0x7fffffe0, -0x7fef7fe0, -0x7fef8000, -0x80000000, -0x7fff8000, 0x100000, 0x20, -0x7fefffe0, 0x108000, 0x100020, -0x7fff7fe0, 0, -0x80000000, 0x8000, 0x108020, -0x7ff00000, 0x100020, -0x7fffffe0, 0, 0x108000, 0x8020, -0x7fef8000, -0x7ff00000, 0x8020, 0, 0x108020, -0x7fefffe0, 0x100000, -0x7fff7fe0, -0x7ff00000, -0x7fef8000, 0x8000, -0x7ff00000, -0x7fff8000, 0x20, -0x7fef7fe0, 0x108020, 0x20, 0x8000, -0x80000000, 0x8020, -0x7fef8000, 0x100000, -0x7fffffe0, 0x100020, -0x7fff7fe0, -0x7fffffe0, 0x100020, 0x108000, 0, -0x7fff8000, 0x8020, -0x80000000, -0x7fefffe0, -0x7fef7fe0, 0x108000);
+ $spfunction3 = array(0x208, 0x8020200, 0, 0x8020008, 0x8000200, 0, 0x20208, 0x8000200, 0x20008, 0x8000008, 0x8000008, 0x20000, 0x8020208, 0x20008, 0x8020000, 0x208, 0x8000000, 0x8, 0x8020200, 0x200, 0x20200, 0x8020000, 0x8020008, 0x20208, 0x8000208, 0x20200, 0x20000, 0x8000208, 0x8, 0x8020208, 0x200, 0x8000000, 0x8020200, 0x8000000, 0x20008, 0x208, 0x20000, 0x8020200, 0x8000200, 0, 0x200, 0x20008, 0x8020208, 0x8000200, 0x8000008, 0x200, 0, 0x8020008, 0x8000208, 0x20000, 0x8000000, 0x8020208, 0x8, 0x20208, 0x20200, 0x8000008, 0x8020000, 0x8000208, 0x208, 0x8020000, 0x20208, 0x8, 0x8020008, 0x20200);
+ $spfunction4 = array(0x802001, 0x2081, 0x2081, 0x80, 0x802080, 0x800081, 0x800001, 0x2001, 0, 0x802000, 0x802000, 0x802081, 0x81, 0, 0x800080, 0x800001, 0x1, 0x2000, 0x800000, 0x802001, 0x80, 0x800000, 0x2001, 0x2080, 0x800081, 0x1, 0x2080, 0x800080, 0x2000, 0x802080, 0x802081, 0x81, 0x800080, 0x800001, 0x802000, 0x802081, 0x81, 0, 0, 0x802000, 0x2080, 0x800080, 0x800081, 0x1, 0x802001, 0x2081, 0x2081, 0x80, 0x802081, 0x81, 0x1, 0x2000, 0x800001, 0x2001, 0x802080, 0x800081, 0x2001, 0x2080, 0x800000, 0x802001, 0x80, 0x800000, 0x2000, 0x802080);
+ $spfunction5 = array(0x100, 0x2080100, 0x2080000, 0x42000100, 0x80000, 0x100, 0x40000000, 0x2080000, 0x40080100, 0x80000, 0x2000100, 0x40080100, 0x42000100, 0x42080000, 0x80100, 0x40000000, 0x2000000, 0x40080000, 0x40080000, 0, 0x40000100, 0x42080100, 0x42080100, 0x2000100, 0x42080000, 0x40000100, 0, 0x42000000, 0x2080100, 0x2000000, 0x42000000, 0x80100, 0x80000, 0x42000100, 0x100, 0x2000000, 0x40000000, 0x2080000, 0x42000100, 0x40080100, 0x2000100, 0x40000000, 0x42080000, 0x2080100, 0x40080100, 0x100, 0x2000000, 0x42080000, 0x42080100, 0x80100, 0x42000000, 0x42080100, 0x2080000, 0, 0x40080000, 0x42000000, 0x80100, 0x2000100, 0x40000100, 0x80000, 0, 0x40080000, 0x2080100, 0x40000100);
+ $spfunction6 = array(0x20000010, 0x20400000, 0x4000, 0x20404010, 0x20400000, 0x10, 0x20404010, 0x400000, 0x20004000, 0x404010, 0x400000, 0x20000010, 0x400010, 0x20004000, 0x20000000, 0x4010, 0, 0x400010, 0x20004010, 0x4000, 0x404000, 0x20004010, 0x10, 0x20400010, 0x20400010, 0, 0x404010, 0x20404000, 0x4010, 0x404000, 0x20404000, 0x20000000, 0x20004000, 0x10, 0x20400010, 0x404000, 0x20404010, 0x400000, 0x4010, 0x20000010, 0x400000, 0x20004000, 0x20000000, 0x4010, 0x20000010, 0x20404010, 0x404000, 0x20400000, 0x404010, 0x20404000, 0, 0x20400010, 0x10, 0x4000, 0x20400000, 0x404010, 0x4000, 0x400010, 0x20004010, 0, 0x20404000, 0x20000000, 0x400010, 0x20004010);
+ $spfunction7 = array(0x200000, 0x4200002, 0x4000802, 0, 0x800, 0x4000802, 0x200802, 0x4200800, 0x4200802, 0x200000, 0, 0x4000002, 0x2, 0x4000000, 0x4200002, 0x802, 0x4000800, 0x200802, 0x200002, 0x4000800, 0x4000002, 0x4200000, 0x4200800, 0x200002, 0x4200000, 0x800, 0x802, 0x4200802, 0x200800, 0x2, 0x4000000, 0x200800, 0x4000000, 0x200800, 0x200000, 0x4000802, 0x4000802, 0x4200002, 0x4200002, 0x2, 0x200002, 0x4000000, 0x4000800, 0x200000, 0x4200800, 0x802, 0x200802, 0x4200800, 0x802, 0x4000002, 0x4200802, 0x4200000, 0x200800, 0, 0x2, 0x4200802, 0, 0x200802, 0x4200000, 0x800, 0x4000002, 0x4000800, 0x800, 0x200002);
+ $spfunction8 = array(0x10001040, 0x1000, 0x40000, 0x10041040, 0x10000000, 0x10001040, 0x40, 0x10000000, 0x40040, 0x10040000, 0x10041040, 0x41000, 0x10041000, 0x41040, 0x1000, 0x40, 0x10040000, 0x10000040, 0x10001000, 0x1040, 0x41000, 0x40040, 0x10040040, 0x10041000, 0x1040, 0, 0, 0x10040040, 0x10000040, 0x10001000, 0x41040, 0x40000, 0x41040, 0x40000, 0x10041000, 0x1000, 0x40, 0x10040040, 0x1000, 0x41040, 0x10001000, 0x40, 0x10000040, 0x10040000, 0x10040040, 0x10000000, 0x40000, 0x10001040, 0, 0x10041040, 0x40040, 0x10000040, 0x10040000, 0x10001000, 0x10001040, 0, 0x10041040, 0x41000, 0x41000, 0x1040, 0x1040, 0x40040, 0x10000000, 0x10041000);
+ $masks = array(4294967295, 2147483647, 1073741823, 536870911, 268435455, 134217727, 67108863, 33554431, 16777215, 8388607, 4194303, 2097151, 1048575, 524287, 262143, 131071, 65535, 32767, 16383, 8191, 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0);
+
+ //create the 16 or 48 subkeys we will need
+ $keys = self::_createKeys($key);
+ $m = 0;
+ $len = strlen($message);
+ $chunk = 0;
+ //set up the loops for single and triple des
+ $iterations = ((count($keys) == 32) ? 3 : 9); //single or triple des
+ if (3 == $iterations) {$looping = (($encrypt) ? array(0, 32, 2) : array(30, -2, -2));} else { $looping = (($encrypt) ? array(0, 32, 2, 62, 30, -2, 64, 96, 2) : array(94, 62, -2, 32, 64, 2, 30, -2, -2));}
+
+ $message .= (chr(0) . chr(0) . chr(0) . chr(0) . chr(0) . chr(0) . chr(0) . chr(0)); //pad the message out with null bytes
+ //store the result here
+ $result = "";
+ $tempresult = "";
+
+ if (1 == $mode) {
+ //CBC mode
+ $cbcleft = (ord($iv{$m++}) << 24) | (ord($iv{$m++}) << 16) | (ord($iv{$m++}) << 8) | ord($iv{$m++});
+ $cbcright = (ord($iv{$m++}) << 24) | (ord($iv{$m++}) << 16) | (ord($iv{$m++}) << 8) | ord($iv{$m++});
+ $m = 0;
+ }
+
+ //loop through each 64 bit chunk of the message
+ while ($m < $len) {
+ $left = (ord($message{$m++}) << 24) | (ord($message{$m++}) << 16) | (ord($message{$m++}) << 8) | ord($message{$m++});
+ $right = (ord($message{$m++}) << 24) | (ord($message{$m++}) << 16) | (ord($message{$m++}) << 8) | ord($message{$m++});
+
+ //for Cipher Block Chaining mode, xor the message with the previous result
+ if (1 == $mode) {
+ if ($encrypt) {$left ^= $cbcleft;
+ $right ^= $cbcright;} else {
+ $cbcleft2 = $cbcleft;
+ $cbcright2 = $cbcright;
+ $cbcleft = $left;
+ $cbcright = $right;}}
+
+ //first each 64 but chunk of the message must be permuted according to IP
+ $temp = (($left >> 4 & $masks[4]) ^ $right) & 0x0f0f0f0f;
+ $right ^= $temp;
+ $left ^= ($temp << 4);
+ $temp = (($left >> 16 & $masks[16]) ^ $right) & 0x0000ffff;
+ $right ^= $temp;
+ $left ^= ($temp << 16);
+ $temp = (($right >> 2 & $masks[2]) ^ $left) & 0x33333333;
+ $left ^= $temp;
+ $right ^= ($temp << 2);
+ $temp = (($right >> 8 & $masks[8]) ^ $left) & 0x00ff00ff;
+ $left ^= $temp;
+ $right ^= ($temp << 8);
+ $temp = (($left >> 1 & $masks[1]) ^ $right) & 0x55555555;
+ $right ^= $temp;
+ $left ^= ($temp << 1);
+
+ $left = (($left << 1) | ($left >> 31 & $masks[31]));
+ $right = (($right << 1) | ($right >> 31 & $masks[31]));
+
+ //do this either 1 or 3 times for each chunk of the message
+ for ($j = 0; $j < $iterations; $j += 3) {
+ $endloop = $looping[$j + 1];
+ $loopinc = $looping[$j + 2];
+ //now go through and perform the encryption or decryption
+ for ($i = $looping[$j]; $i != $endloop; $i += $loopinc) {
+ //for efficiency
+ $right1 = $right ^ $keys[$i];
+ $right2 = (($right >> 4 & $masks[4]) | ($right << 28)) ^ $keys[$i + 1];
+ //the result is attained by passing these bytes through the S selection functions
+ $temp = $left;
+ $left = $right;
+ $right = $temp ^ ($spfunction2[($right1 >> 24 & $masks[24]) & 0x3f] | $spfunction4[($right1 >> 16 & $masks[16]) & 0x3f]
+ | $spfunction6[($right1 >> 8 & $masks[8]) & 0x3f] | $spfunction8[$right1 & 0x3f]
+ | $spfunction1[($right2 >> 24 & $masks[24]) & 0x3f] | $spfunction3[($right2 >> 16 & $masks[16]) & 0x3f]
+ | $spfunction5[($right2 >> 8 & $masks[8]) & 0x3f] | $spfunction7[$right2 & 0x3f]);
+ }
+ $temp = $left;
+ $left = $right;
+ $right = $temp; //unreverse left and right
+ } //for either 1 or 3 iterations
+
+ //move then each one bit to the right
+ $left = (($left >> 1 & $masks[1]) | ($left << 31));
+ $right = (($right >> 1 & $masks[1]) | ($right << 31));
+
+ //now perform IP-1, which is IP in the opposite direction
+ $temp = (($left >> 1 & $masks[1]) ^ $right) & 0x55555555;
+ $right ^= $temp;
+ $left ^= ($temp << 1);
+ $temp = (($right >> 8 & $masks[8]) ^ $left) & 0x00ff00ff;
+ $left ^= $temp;
+ $right ^= ($temp << 8);
+ $temp = (($right >> 2 & $masks[2]) ^ $left) & 0x33333333;
+ $left ^= $temp;
+ $right ^= ($temp << 2);
+ $temp = (($left >> 16 & $masks[16]) ^ $right) & 0x0000ffff;
+ $right ^= $temp;
+ $left ^= ($temp << 16);
+ $temp = (($left >> 4 & $masks[4]) ^ $right) & 0x0f0f0f0f;
+ $right ^= $temp;
+ $left ^= ($temp << 4);
+
+ //for Cipher Block Chaining mode, xor the message with the previous result
+ if (1 == $mode) {
+ if ($encrypt) {$cbcleft = $left;
+ $cbcright = $right;} else {
+ $left ^= $cbcleft2;
+ $right ^= $cbcright2;}}
+ $tempresult .= (chr($left >> 24 & $masks[24]) . chr(($left >> 16 & $masks[16]) & 0xff) . chr(($left >> 8 & $masks[8]) & 0xff) . chr($left & 0xff) . chr($right >> 24 & $masks[24]) . chr(($right >> 16 & $masks[16]) & 0xff) . chr(($right >> 8 & $masks[8]) & 0xff) . chr($right & 0xff));
+
+ $chunk += 8;
+ if (512 == $chunk) {
+ $result .= $tempresult;
+ $tempresult = "";
+ $chunk = 0;}
+ } //for every 8 characters, or 64 bits in the message
+
+ //return the result as an array
+ return ($result . $tempresult);
+ } //end of des
+
+ /**
+ * createKeys
+ * this takes as input a 64 bit key (even though only 56 bits are used)
+ * as an array of 2 integers, and returns 16 48 bit keys
+ * @param string $key 加密key
+ * @return string
+ */
+ private static function _createKeys($key)
+ {
+ //declaring this locally speeds things up a bit
+ $pc2bytes0 = array(0, 0x4, 0x20000000, 0x20000004, 0x10000, 0x10004, 0x20010000, 0x20010004, 0x200, 0x204, 0x20000200, 0x20000204, 0x10200, 0x10204, 0x20010200, 0x20010204);
+ $pc2bytes1 = array(0, 0x1, 0x100000, 0x100001, 0x4000000, 0x4000001, 0x4100000, 0x4100001, 0x100, 0x101, 0x100100, 0x100101, 0x4000100, 0x4000101, 0x4100100, 0x4100101);
+ $pc2bytes2 = array(0, 0x8, 0x800, 0x808, 0x1000000, 0x1000008, 0x1000800, 0x1000808, 0, 0x8, 0x800, 0x808, 0x1000000, 0x1000008, 0x1000800, 0x1000808);
+ $pc2bytes3 = array(0, 0x200000, 0x8000000, 0x8200000, 0x2000, 0x202000, 0x8002000, 0x8202000, 0x20000, 0x220000, 0x8020000, 0x8220000, 0x22000, 0x222000, 0x8022000, 0x8222000);
+ $pc2bytes4 = array(0, 0x40000, 0x10, 0x40010, 0, 0x40000, 0x10, 0x40010, 0x1000, 0x41000, 0x1010, 0x41010, 0x1000, 0x41000, 0x1010, 0x41010);
+ $pc2bytes5 = array(0, 0x400, 0x20, 0x420, 0, 0x400, 0x20, 0x420, 0x2000000, 0x2000400, 0x2000020, 0x2000420, 0x2000000, 0x2000400, 0x2000020, 0x2000420);
+ $pc2bytes6 = array(0, 0x10000000, 0x80000, 0x10080000, 0x2, 0x10000002, 0x80002, 0x10080002, 0, 0x10000000, 0x80000, 0x10080000, 0x2, 0x10000002, 0x80002, 0x10080002);
+ $pc2bytes7 = array(0, 0x10000, 0x800, 0x10800, 0x20000000, 0x20010000, 0x20000800, 0x20010800, 0x20000, 0x30000, 0x20800, 0x30800, 0x20020000, 0x20030000, 0x20020800, 0x20030800);
+ $pc2bytes8 = array(0, 0x40000, 0, 0x40000, 0x2, 0x40002, 0x2, 0x40002, 0x2000000, 0x2040000, 0x2000000, 0x2040000, 0x2000002, 0x2040002, 0x2000002, 0x2040002);
+ $pc2bytes9 = array(0, 0x10000000, 0x8, 0x10000008, 0, 0x10000000, 0x8, 0x10000008, 0x400, 0x10000400, 0x408, 0x10000408, 0x400, 0x10000400, 0x408, 0x10000408);
+ $pc2bytes10 = array(0, 0x20, 0, 0x20, 0x100000, 0x100020, 0x100000, 0x100020, 0x2000, 0x2020, 0x2000, 0x2020, 0x102000, 0x102020, 0x102000, 0x102020);
+ $pc2bytes11 = array(0, 0x1000000, 0x200, 0x1000200, 0x200000, 0x1200000, 0x200200, 0x1200200, 0x4000000, 0x5000000, 0x4000200, 0x5000200, 0x4200000, 0x5200000, 0x4200200, 0x5200200);
+ $pc2bytes12 = array(0, 0x1000, 0x8000000, 0x8001000, 0x80000, 0x81000, 0x8080000, 0x8081000, 0x10, 0x1010, 0x8000010, 0x8001010, 0x80010, 0x81010, 0x8080010, 0x8081010);
+ $pc2bytes13 = array(0, 0x4, 0x100, 0x104, 0, 0x4, 0x100, 0x104, 0x1, 0x5, 0x101, 0x105, 0x1, 0x5, 0x101, 0x105);
+ $masks = array(4294967295, 2147483647, 1073741823, 536870911, 268435455, 134217727, 67108863, 33554431, 16777215, 8388607, 4194303, 2097151, 1048575, 524287, 262143, 131071, 65535, 32767, 16383, 8191, 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0);
+
+ //how many iterations (1 for des, 3 for triple des)
+ $iterations = ((strlen($key) >= 24) ? 3 : 1);
+ //stores the return keys
+ $keys = array(); // size = 32 * iterations but you don't specify this in php
+ //now define the left shifts which need to be done
+ $shifts = array(0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0);
+ //other variables
+ $m = 0;
+ $n = 0;
+
+ for ($j = 0; $j < $iterations; $j++) {
+ //either 1 or 3 iterations
+ $left = (ord($key{$m++}) << 24) | (ord($key{$m++}) << 16) | (ord($key{$m++}) << 8) | ord($key{$m++});
+ $right = (ord($key{$m++}) << 24) | (ord($key{$m++}) << 16) | (ord($key{$m++}) << 8) | ord($key{$m++});
+
+ $temp = (($left >> 4 & $masks[4]) ^ $right) & 0x0f0f0f0f;
+ $right ^= $temp;
+ $left ^= ($temp << 4);
+ $temp = (($right >> 16 & $masks[16]) ^ $left) & 0x0000ffff;
+ $left ^= $temp;
+ $right ^= ($temp << -16);
+ $temp = (($left >> 2 & $masks[2]) ^ $right) & 0x33333333;
+ $right ^= $temp;
+ $left ^= ($temp << 2);
+ $temp = (($right >> 16 & $masks[16]) ^ $left) & 0x0000ffff;
+ $left ^= $temp;
+ $right ^= ($temp << -16);
+ $temp = (($left >> 1 & $masks[1]) ^ $right) & 0x55555555;
+ $right ^= $temp;
+ $left ^= ($temp << 1);
+ $temp = (($right >> 8 & $masks[8]) ^ $left) & 0x00ff00ff;
+ $left ^= $temp;
+ $right ^= ($temp << 8);
+ $temp = (($left >> 1 & $masks[1]) ^ $right) & 0x55555555;
+ $right ^= $temp;
+ $left ^= ($temp << 1);
+
+ //the right side needs to be shifted and to get the last four bits of the left side
+ $temp = ($left << 8) | (($right >> 20 & $masks[20]) & 0x000000f0);
+ //left needs to be put upside down
+ $left = ($right << 24) | (($right << 8) & 0xff0000) | (($right >> 8 & $masks[8]) & 0xff00) | (($right >> 24 & $masks[24]) & 0xf0);
+ $right = $temp;
+
+ //now go through and perform these shifts on the left and right keys
+ for ($i = 0; $i < count($shifts); $i++) {
+ //shift the keys either one or two bits to the left
+ if ($shifts[$i] > 0) {
+ $left = (($left << 2) | ($left >> 26 & $masks[26]));
+ $right = (($right << 2) | ($right >> 26 & $masks[26]));
+ } else {
+ $left = (($left << 1) | ($left >> 27 & $masks[27]));
+ $right = (($right << 1) | ($right >> 27 & $masks[27]));
+ }
+ $left = $left & -0xf;
+ $right = $right & -0xf;
+
+ //now apply PC-2, in such a way that E is easier when encrypting or decrypting
+ //this conversion will look like PC-2 except only the last 6 bits of each byte are used
+ //rather than 48 consecutive bits and the order of lines will be according to
+ //how the S selection functions will be applied: S2, S4, S6, S8, S1, S3, S5, S7
+ $lefttemp = $pc2bytes0[$left >> 28 & $masks[28]] | $pc2bytes1[($left >> 24 & $masks[24]) & 0xf]
+ | $pc2bytes2[($left >> 20 & $masks[20]) & 0xf] | $pc2bytes3[($left >> 16 & $masks[16]) & 0xf]
+ | $pc2bytes4[($left >> 12 & $masks[12]) & 0xf] | $pc2bytes5[($left >> 8 & $masks[8]) & 0xf]
+ | $pc2bytes6[($left >> 4 & $masks[4]) & 0xf];
+ $righttemp = $pc2bytes7[$right >> 28 & $masks[28]] | $pc2bytes8[($right >> 24 & $masks[24]) & 0xf]
+ | $pc2bytes9[($right >> 20 & $masks[20]) & 0xf] | $pc2bytes10[($right >> 16 & $masks[16]) & 0xf]
+ | $pc2bytes11[($right >> 12 & $masks[12]) & 0xf] | $pc2bytes12[($right >> 8 & $masks[8]) & 0xf]
+ | $pc2bytes13[($right >> 4 & $masks[4]) & 0xf];
+ $temp = (($righttemp >> 16 & $masks[16]) ^ $lefttemp) & 0x0000ffff;
+ $keys[$n++] = $lefttemp ^ $temp;
+ $keys[$n++] = $righttemp ^ ($temp << 16);
+ }
+ } //for each iterations
+ //return the keys we've created
+ return $keys;
+ } //end of des_createKeys
+
+}
diff --git a/Framework/Library/Think/Crypt/Driver/Think.class.php b/Framework/Library/Think/Crypt/Driver/Think.class.php
new file mode 100644
index 00000000..9a65a3a1
--- /dev/null
+++ b/Framework/Library/Think/Crypt/Driver/Think.class.php
@@ -0,0 +1,96 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Crypt\Driver;
+
+/**
+ * Base64 加密实现类
+ */
+class Think
+{
+
+ /**
+ * 加密字符串
+ * @param string $str 字符串
+ * @param string $key 加密key
+ * @param integer $expire 有效期(秒)
+ * @return string
+ */
+ public static function encrypt($data, $key, $expire = 0)
+ {
+ $expire = sprintf('%010d', $expire ? $expire + time() : 0);
+ $key = md5($key);
+ $data = base64_encode($expire . $data);
+ $x = 0;
+ $len = strlen($data);
+ $l = strlen($key);
+ $char = $str = '';
+
+ for ($i = 0; $i < $len; $i++) {
+ if ($x == $l) {
+ $x = 0;
+ }
+
+ $char .= substr($key, $x, 1);
+ $x++;
+ }
+
+ for ($i = 0; $i < $len; $i++) {
+ $str .= chr(ord(substr($data, $i, 1)) + (ord(substr($char, $i, 1))) % 256);
+ }
+ return str_replace(array('+', '/', '='), array('-', '_', ''), base64_encode($str));
+ }
+
+ /**
+ * 解密字符串
+ * @param string $str 字符串
+ * @param string $key 加密key
+ * @return string
+ */
+ public static function decrypt($data, $key)
+ {
+ $key = md5($key);
+ $data = str_replace(array('-', '_'), array('+', '/'), $data);
+ $mod4 = strlen($data) % 4;
+ if ($mod4) {
+ $data .= substr('====', $mod4);
+ }
+ $data = base64_decode($data);
+
+ $x = 0;
+ $len = strlen($data);
+ $l = strlen($key);
+ $char = $str = '';
+
+ for ($i = 0; $i < $len; $i++) {
+ if ($x == $l) {
+ $x = 0;
+ }
+
+ $char .= substr($key, $x, 1);
+ $x++;
+ }
+
+ for ($i = 0; $i < $len; $i++) {
+ if (ord(substr($data, $i, 1)) < ord(substr($char, $i, 1))) {
+ $str .= chr((ord(substr($data, $i, 1)) + 256) - ord(substr($char, $i, 1)));
+ } else {
+ $str .= chr(ord(substr($data, $i, 1)) - ord(substr($char, $i, 1)));
+ }
+ }
+ $data = base64_decode($str);
+ $expire = substr($data, 0, 10);
+ if ($expire > 0 && $expire < time()) {
+ return '';
+ }
+ $data = substr($data, 10);
+ return $data;
+ }
+}
diff --git a/Framework/Library/Think/Crypt/Driver/Xxtea.class.php b/Framework/Library/Think/Crypt/Driver/Xxtea.class.php
new file mode 100644
index 00000000..9677b339
--- /dev/null
+++ b/Framework/Library/Think/Crypt/Driver/Xxtea.class.php
@@ -0,0 +1,129 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Crypt\Driver;
+
+/**
+ * Xxtea 加密实现类
+ */
+class Xxtea
+{
+
+ /**
+ * 加密字符串
+ * @param string $str 字符串
+ * @param string $key 加密key
+ * @param integer $expire 有效期(秒)
+ * @return string
+ */
+ public static function encrypt($str, $key, $expire = 0)
+ {
+ $expire = sprintf('%010d', $expire ? $expire + time() : 0);
+ $str = $expire . $str;
+ $v = self::str2long($str, true);
+ $k = self::str2long($key, false);
+ $n = count($v) - 1;
+
+ $z = $v[$n];
+ $y = $v[0];
+ $delta = 0x9E3779B9;
+ $q = floor(6 + 52 / ($n + 1));
+ $sum = 0;
+ while (0 < $q--) {
+ $sum = self::int32($sum + $delta);
+ $e = $sum >> 2 & 3;
+ for ($p = 0; $p < $n; $p++) {
+ $y = $v[$p + 1];
+ $mx = self::int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ self::int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
+ $z = $v[$p] = self::int32($v[$p] + $mx);
+ }
+ $y = $v[0];
+ $mx = self::int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ self::int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
+ $z = $v[$n] = self::int32($v[$n] + $mx);
+ }
+ return self::long2str($v, false);
+ }
+
+ /**
+ * 解密字符串
+ * @param string $str 字符串
+ * @param string $key 加密key
+ * @return string
+ */
+ public static function decrypt($str, $key)
+ {
+ $v = self::str2long($str, false);
+ $k = self::str2long($key, false);
+ $n = count($v) - 1;
+
+ $z = $v[$n];
+ $y = $v[0];
+ $delta = 0x9E3779B9;
+ $q = floor(6 + 52 / ($n + 1));
+ $sum = self::int32($q * $delta);
+ while (0 != $sum) {
+ $e = $sum >> 2 & 3;
+ for ($p = $n; $p > 0; $p--) {
+ $z = $v[$p - 1];
+ $mx = self::int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ self::int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
+ $y = $v[$p] = self::int32($v[$p] - $mx);
+ }
+ $z = $v[$n];
+ $mx = self::int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ self::int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
+ $y = $v[0] = self::int32($v[0] - $mx);
+ $sum = self::int32($sum - $delta);
+ }
+ $data = self::long2str($v, true);
+ $expire = substr($data, 0, 10);
+ if ($expire > 0 && $expire < time()) {
+ return '';
+ }
+ $data = substr($data, 10);
+ return $data;
+ }
+
+ private static function long2str($v, $w)
+ {
+ $len = count($v);
+ $s = array();
+ for ($i = 0; $i < $len; $i++) {
+ $s[$i] = pack("V", $v[$i]);
+ }
+ if ($w) {
+ return substr(join('', $s), 0, $v[$len - 1]);
+ } else {
+ return join('', $s);
+ }
+ }
+
+ private static function str2long($s, $w)
+ {
+ $v = unpack("V*", $s . str_repeat("\0", (4 - strlen($s) % 4) & 3));
+ $v = array_values($v);
+ if ($w) {
+ $v[count($v)] = strlen($s);
+ }
+ return $v;
+ }
+
+ private static function int32($n)
+ {
+ while ($n >= 2147483648) {
+ $n -= 4294967296;
+ }
+
+ while ($n <= -2147483649) {
+ $n += 4294967296;
+ }
+
+ return (int) $n;
+ }
+
+}
diff --git a/Framework/Library/Think/Db.class.php b/Framework/Library/Think/Db.class.php
new file mode 100644
index 00000000..0a711760
--- /dev/null
+++ b/Framework/Library/Think/Db.class.php
@@ -0,0 +1,145 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think;
+
+/**
+ * ThinkPHP 数据库中间层实现类
+ */
+class Db
+{
+
+ private static $instance = array(); // 数据库连接实例
+ private static $_instance = null; // 当前数据库连接实例
+
+ /**
+ * 取得数据库类实例
+ * @static
+ * @access public
+ * @param mixed $config 连接配置
+ * @return Object 返回数据库驱动类
+ */
+ public static function getInstance($config = array())
+ {
+ $md5 = md5(serialize($config));
+ if (!isset(self::$instance[$md5])) {
+ // 解析连接参数 支持数组和字符串
+ $options = self::parseConfig($config);
+ // 兼容mysqli
+ if ('mysqli' == $options['type']) {
+ $options['type'] = 'mysql';
+ }
+
+ // 如果采用lite方式 仅支持原生SQL 包括query和execute方法
+ $class = !empty($options['lite']) ? 'Think\Db\Lite' : 'Think\\Db\\Driver\\' . ucwords(strtolower($options['type']));
+ if (class_exists($class)) {
+ self::$instance[$md5] = new $class($options);
+ } else {
+ // 类没有定义
+ E(L('_NO_DB_DRIVER_') . ': ' . $class);
+ }
+ }
+ self::$_instance = self::$instance[$md5];
+ return self::$_instance;
+ }
+
+ /**
+ * 数据库连接参数解析
+ * @static
+ * @access private
+ * @param mixed $config
+ * @return array
+ */
+ private static function parseConfig($config)
+ {
+ if (!empty($config)) {
+ if (is_string($config)) {
+ return self::parseDsn($config);
+ }
+ $config = array_change_key_case($config);
+ $config = array(
+ 'type' => $config['db_type'],
+ 'username' => $config['db_user'],
+ 'password' => $config['db_pwd'],
+ 'hostname' => $config['db_host'],
+ 'hostport' => $config['db_port'],
+ 'database' => $config['db_name'],
+ 'dsn' => isset($config['db_dsn']) ? $config['db_dsn'] : null,
+ 'params' => isset($config['db_params']) ? $config['db_params'] : null,
+ 'charset' => isset($config['db_charset']) ? $config['db_charset'] : 'utf8',
+ 'deploy' => isset($config['db_deploy_type']) ? $config['db_deploy_type'] : 0,
+ 'rw_separate' => isset($config['db_rw_separate']) ? $config['db_rw_separate'] : false,
+ 'master_num' => isset($config['db_master_num']) ? $config['db_master_num'] : 1,
+ 'slave_no' => isset($config['db_slave_no']) ? $config['db_slave_no'] : '',
+ 'debug' => isset($config['db_debug']) ? $config['db_debug'] : APP_DEBUG,
+ 'lite' => isset($config['db_lite']) ? $config['db_lite'] : false,
+ );
+ } else {
+ $config = array(
+ 'type' => C('DB_TYPE'),
+ 'username' => C('DB_USER'),
+ 'password' => C('DB_PWD'),
+ 'hostname' => C('DB_HOST'),
+ 'hostport' => C('DB_PORT'),
+ 'database' => C('DB_NAME'),
+ 'dsn' => C('DB_DSN'),
+ 'params' => C('DB_PARAMS'),
+ 'charset' => C('DB_CHARSET'),
+ 'deploy' => C('DB_DEPLOY_TYPE'),
+ 'rw_separate' => C('DB_RW_SEPARATE'),
+ 'master_num' => C('DB_MASTER_NUM'),
+ 'slave_no' => C('DB_SLAVE_NO'),
+ 'debug' => C('DB_DEBUG', null, APP_DEBUG),
+ 'lite' => C('DB_LITE'),
+ );
+ }
+ return $config;
+ }
+
+ /**
+ * DSN解析
+ * 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1¶m2=val2#utf8
+ * @static
+ * @access private
+ * @param string $dsnStr
+ * @return array
+ */
+ private static function parseDsn($dsnStr)
+ {
+ if (empty($dsnStr)) {return false;}
+ $info = parse_url($dsnStr);
+ if (!$info) {
+ return false;
+ }
+ $dsn = array(
+ 'type' => $info['scheme'],
+ 'username' => isset($info['user']) ? $info['user'] : '',
+ 'password' => isset($info['pass']) ? $info['pass'] : '',
+ 'hostname' => isset($info['host']) ? $info['host'] : '',
+ 'hostport' => isset($info['port']) ? $info['port'] : '',
+ 'database' => isset($info['path']) ? ltrim($info['path'], '/') : '',
+ 'charset' => isset($info['fragment']) ? $info['fragment'] : 'utf8',
+ );
+
+ if (isset($info['query'])) {
+ parse_str($info['query'], $dsn['params']);
+ } else {
+ $dsn['params'] = array();
+ }
+ return $dsn;
+ }
+
+ // 调用驱动类的方法
+ public static function __callStatic($method, $params)
+ {
+ return call_user_func_array(array(self::$_instance, $method), $params);
+ }
+}
diff --git a/Framework/Library/Think/Db/Driver.class.php b/Framework/Library/Think/Db/Driver.class.php
new file mode 100644
index 00000000..dc45c116
--- /dev/null
+++ b/Framework/Library/Think/Db/Driver.class.php
@@ -0,0 +1,1292 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think\Db;
+
+use PDO;
+use Think\Config;
+use Think\Debug;
+
+abstract class Driver
+{
+ // PDO操作实例
+ protected $PDOStatement = null;
+ // 当前操作所属的模型名
+ protected $model = '_think_';
+ // 当前SQL指令
+ protected $queryStr = '';
+ protected $modelSql = array();
+ // 最后插入ID
+ protected $lastInsID = null;
+ // 返回或者影响记录数
+ protected $numRows = 0;
+ // 事物操作PDO实例
+ protected $transPDO = null;
+ // 事务指令数
+ protected $transTimes = 0;
+ // 错误信息
+ protected $error = '';
+ // 数据库连接ID 支持多个连接
+ protected $linkID = array();
+ // 当前连接ID
+ protected $_linkID = null;
+ // 数据库连接参数配置
+ protected $config = array(
+ 'type' => '', // 数据库类型
+ 'hostname' => '127.0.0.1', // 服务器地址
+ 'database' => '', // 数据库名
+ 'username' => '', // 用户名
+ 'password' => '', // 密码
+ 'hostport' => '', // 端口
+ 'dsn' => '', //
+ 'params' => array(), // 数据库连接参数
+ 'charset' => 'utf8', // 数据库编码默认采用utf8
+ 'prefix' => '', // 数据库表前缀
+ 'debug' => false, // 数据库调试模式
+ 'deploy' => 0, // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
+ 'rw_separate' => false, // 数据库读写是否分离 主从式有效
+ 'master_num' => 1, // 读写分离后 主服务器数量
+ 'slave_no' => '', // 指定从服务器序号
+ 'db_like_fields' => '',
+ );
+ // 数据库表达式
+ protected $exp = array('eq' => '=', 'neq' => '<>', 'gt' => '>', 'egt' => '>=', 'lt' => '<', 'elt' => '<=', 'notlike' => 'NOT LIKE', 'like' => 'LIKE', 'in' => 'IN', 'notin' => 'NOT IN', 'not in' => 'NOT IN', 'between' => 'BETWEEN', 'not between' => 'NOT BETWEEN', 'notbetween' => 'NOT BETWEEN');
+ // 查询表达式
+ protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%%LIMIT% %UNION%%LOCK%%COMMENT%';
+ // 查询次数
+ protected $queryTimes = 0;
+ // 执行次数
+ protected $executeTimes = 0;
+ // PDO连接参数
+ protected $options = array(
+ PDO::ATTR_CASE => PDO::CASE_LOWER,
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+ PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
+ PDO::ATTR_STRINGIFY_FETCHES => false,
+ );
+ protected $bind = array(); // 参数绑定
+
+ /**
+ * 架构函数 读取数据库配置信息
+ * @access public
+ * @param array $config 数据库配置数组
+ */
+ public function __construct($config = '')
+ {
+ if (!empty($config)) {
+ $this->config = array_merge($this->config, $config);
+ if (is_array($this->config['params'])) {
+ $this->options = $this->config['params'] + $this->options;
+ }
+ }
+ }
+
+ /**
+ * 连接数据库方法
+ * @access public
+ */
+ public function connect($config = '', $linkNum = 0, $autoConnection = false)
+ {
+ if (!isset($this->linkID[$linkNum])) {
+ if (empty($config)) {
+ $config = $this->config;
+ }
+
+ try {
+ if (empty($config['dsn'])) {
+ $config['dsn'] = $this->parseDsn($config);
+ }
+ if (version_compare(PHP_VERSION, '5.3.6', '<=')) {
+ // 禁用模拟预处理语句
+ $this->options[PDO::ATTR_EMULATE_PREPARES] = false;
+ }
+ $this->linkID[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $this->options);
+ } catch (\PDOException $e) {
+ if ($autoConnection) {
+ trace($e->getMessage(), '', 'ERR');
+ return $this->connect($autoConnection, $linkNum);
+ } elseif ($config['debug']) {
+ E($e->getMessage());
+ }
+ }
+ }
+ return $this->linkID[$linkNum];
+ }
+
+ /**
+ * 解析pdo连接的dsn信息
+ * @access public
+ * @param array $config 连接信息
+ * @return string
+ */
+ protected function parseDsn($config)
+ {}
+
+ /**
+ * 释放查询结果
+ * @access public
+ */
+ public function free()
+ {
+ $this->PDOStatement = null;
+ }
+
+ /**
+ * 执行查询 返回数据集
+ * @access public
+ * @param string $str sql指令
+ * @param boolean $fetchSql 不执行只是获取SQL
+ * @param boolean $master 是否在主服务器读操作
+ * @return mixed
+ */
+ public function query($str, $fetchSql = false, $master = false)
+ {
+ $this->initConnect($master);
+ if (!$this->_linkID) {
+ return false;
+ }
+
+ $this->queryStr = $str;
+ if (!empty($this->bind)) {
+ $that = $this;
+ $this->queryStr = strtr($this->queryStr, array_map(function ($val) use ($that) {return '\'' . $that->escapeString($val) . '\'';}, $this->bind));
+ }
+ if ($fetchSql) {
+ return $this->queryStr;
+ }
+ //释放前次的查询结果
+ if (!empty($this->PDOStatement)) {
+ $this->free();
+ }
+
+ $this->queryTimes++;
+ N('db_query', 1); // 兼容代码
+ // 调试开始
+ $this->debug(true);
+ $this->PDOStatement = $this->_linkID->prepare($str);
+ if (false === $this->PDOStatement) {
+ $this->error();
+ return false;
+ }
+ foreach ($this->bind as $key => $val) {
+ if (is_array($val)) {
+ $this->PDOStatement->bindValue($key, $val[0], $val[1]);
+ } else {
+ $this->PDOStatement->bindValue($key, $val);
+ }
+ }
+ $this->bind = array();
+ try {
+ $result = $this->PDOStatement->execute();
+ // 调试结束
+ $this->debug(false);
+ if (false === $result) {
+ $this->error();
+ return false;
+ } else {
+ return $this->getResult();
+ }
+ } catch (\PDOException $e) {
+ $this->error();
+ return false;
+ }
+ }
+
+ /**
+ * 执行语句
+ * @access public
+ * @param string $str sql指令
+ * @param boolean $fetchSql 不执行只是获取SQL
+ * @return mixed
+ */
+ public function execute($str, $fetchSql = false)
+ {
+ $this->initConnect(true);
+ if (!$this->_linkID) {
+ return false;
+ }
+
+ $this->queryStr = $str;
+ if (!empty($this->bind)) {
+ $that = $this;
+ $this->queryStr = strtr($this->queryStr, array_map(function ($val) use ($that) {return '\'' . $that->escapeString($val) . '\'';}, $this->bind));
+ }
+ if ($fetchSql) {
+ return $this->queryStr;
+ }
+ //释放前次的查询结果
+ if (!empty($this->PDOStatement)) {
+ $this->free();
+ }
+
+ $this->executeTimes++;
+ N('db_write', 1); // 兼容代码
+ // 记录开始执行时间
+ $this->debug(true);
+ $this->PDOStatement = $this->_linkID->prepare($str);
+ if (false === $this->PDOStatement) {
+ $this->error();
+ return false;
+ }
+ foreach ($this->bind as $key => $val) {
+ if (is_array($val)) {
+ $this->PDOStatement->bindValue($key, $val[0], $val[1]);
+ } else {
+ $this->PDOStatement->bindValue($key, $val);
+ }
+ }
+ $this->bind = array();
+ try {
+ $result = $this->PDOStatement->execute();
+ // 调试结束
+ $this->debug(false);
+ if (false === $result) {
+ $this->error();
+ return false;
+ } else {
+ $this->numRows = $this->PDOStatement->rowCount();
+ if (preg_match("/^\s*(INSERT\s+INTO|REPLACE\s+INTO)\s+/i", $str)) {
+ $this->lastInsID = $this->_linkID->lastInsertId();
+ }
+ return $this->numRows;
+ }
+ } catch (\PDOException $e) {
+ $this->error();
+ return false;
+ }
+ }
+
+ /**
+ * 启动事务
+ * @access public
+ * @return void
+ */
+ public function startTrans()
+ {
+ $this->initConnect(true);
+ if (!$this->_linkID) {
+ return false;
+ }
+
+ //数据rollback 支持
+ if (0 == $this->transTimes) {
+ // 记录当前操作PDO
+ $this->transPdo = $this->_linkID;
+ $this->_linkID->beginTransaction();
+ }
+ $this->transTimes++;
+ return;
+ }
+
+ /**
+ * 用于非自动提交状态下面的查询提交
+ * @access public
+ * @return boolean
+ */
+ public function commit()
+ {
+ if ($this->transTimes == 1) {
+ // 由嵌套事物的最外层进行提交
+ $result = $this->_linkID->commit();
+ $this->transTimes = 0;
+ $this->transPdo = null;
+ if (!$result) {
+ $this->error();
+ return false;
+ }
+ } else {
+ $this->transTimes--;
+ }
+ return true;
+ }
+
+ /**
+ * 事务回滚
+ * @access public
+ * @return boolean
+ */
+ public function rollback()
+ {
+ if ($this->transTimes > 0) {
+ $result = $this->_linkID->rollback();
+ $this->transTimes = 0;
+ $this->transPdo = null;
+ if (!$result) {
+ $this->error();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 获得所有的查询数据
+ * @access private
+ * @return array
+ */
+ private function getResult()
+ {
+ //返回数据集
+ $result = $this->PDOStatement->fetchAll(PDO::FETCH_ASSOC);
+ $this->numRows = count($result);
+ return $result;
+ }
+
+ /**
+ * 获得查询次数
+ * @access public
+ * @param boolean $execute 是否包含所有查询
+ * @return integer
+ */
+ public function getQueryTimes($execute = false)
+ {
+ return $execute ? $this->queryTimes + $this->executeTimes : $this->queryTimes;
+ }
+
+ /**
+ * 获得执行次数
+ * @access public
+ * @return integer
+ */
+ public function getExecuteTimes()
+ {
+ return $this->executeTimes;
+ }
+
+ /**
+ * 关闭数据库
+ * @access public
+ */
+ public function close()
+ {
+ $this->_linkID = null;
+ }
+
+ /**
+ * 数据库错误信息
+ * 并显示当前的SQL语句
+ * @access public
+ * @return string
+ */
+ public function error()
+ {
+ if ($this->PDOStatement) {
+ $error = $this->PDOStatement->errorInfo();
+ $this->error = $error[1] . ':' . $error[2];
+ } else {
+ $this->error = '';
+ }
+ if ('' != $this->queryStr) {
+ $this->error .= "\n [ SQL语句 ] : " . $this->queryStr;
+ }
+ // 记录错误日志
+ trace($this->error, '', 'ERR');
+ if ($this->config['debug']) {
+ // 开启数据库调试模式
+ E($this->error);
+ } else {
+ return $this->error;
+ }
+ }
+
+ /**
+ * 设置锁机制
+ * @access protected
+ * @return string
+ */
+ protected function parseLock($lock = false)
+ {
+ return $lock ? ' FOR UPDATE ' : '';
+ }
+
+ /**
+ * set分析
+ * @access protected
+ * @param array $data
+ * @return string
+ */
+ protected function parseSet($data)
+ {
+ foreach ($data as $key => $val) {
+ if (isset($val[0]) && 'exp' == $val[0]) {
+ $set[] = $this->parseKey($key) . '=' . $val[1];
+ } elseif (is_null($val)) {
+ $set[] = $this->parseKey($key) . '=NULL';
+ } elseif (is_scalar($val)) {
+ // 过滤非标量数据
+ if (0 === strpos($val, ':') && in_array($val, array_keys($this->bind))) {
+ $set[] = $this->parseKey($key) . '=' . $val;
+ } else {
+ $name = count($this->bind);
+ $set[] = $this->parseKey($key) . '=:' . $key . '_' . $name;
+ $this->bindParam($key . '_' . $name, $val);
+ }
+ }
+ }
+ return ' SET ' . implode(',', $set);
+ }
+
+ /**
+ * 参数绑定
+ * @access protected
+ * @param string $name 绑定参数名
+ * @param mixed $value 绑定值
+ * @return void
+ */
+ protected function bindParam($name, $value)
+ {
+ $this->bind[':' . $name] = $value;
+ }
+
+ /**
+ * 字段名分析
+ * @access protected
+ * @param string $key
+ * @return string
+ */
+ protected function parseKey($key)
+ {
+ return $key;
+ }
+
+ /**
+ * value分析
+ * @access protected
+ * @param mixed $value
+ * @return string
+ */
+ protected function parseValue($value)
+ {
+ if (is_string($value)) {
+ $value = strpos($value, ':') === 0 && in_array($value, array_keys($this->bind)) ? $this->escapeString($value) : '\'' . $this->escapeString($value) . '\'';
+ } elseif (isset($value[0]) && is_string($value[0]) && strtolower($value[0]) == 'exp') {
+ $value = $this->escapeString($value[1]);
+ } elseif (is_array($value)) {
+ $value = array_map(array($this, 'parseValue'), $value);
+ } elseif (is_bool($value)) {
+ $value = $value ? '1' : '0';
+ } elseif (is_null($value)) {
+ $value = 'null';
+ }
+ return $value;
+ }
+
+ /**
+ * field分析
+ * @access protected
+ * @param mixed $fields
+ * @return string
+ */
+ protected function parseField($fields)
+ {
+ if (is_string($fields) && '' !== $fields) {
+ $fields = explode(',', $fields);
+ }
+ if (is_array($fields)) {
+ // 完善数组方式传字段名的支持
+ // 支持 'field1'=>'field2' 这样的字段别名定义
+ $array = array();
+ foreach ($fields as $key => $field) {
+ if (!is_numeric($key)) {
+ $array[] = $this->parseKey($key) . ' AS ' . $this->parseKey($field);
+ } else {
+ $array[] = $this->parseKey($field);
+ }
+
+ }
+ $fieldsStr = implode(',', $array);
+ } else {
+ $fieldsStr = '*';
+ }
+ //TODO 如果是查询全部字段,并且是join的方式,那么就把要查的表加个别名,以免字段被覆盖
+ return $fieldsStr;
+ }
+
+ /**
+ * table分析
+ * @access protected
+ * @param mixed $table
+ * @return string
+ */
+ protected function parseTable($tables)
+ {
+ if (is_array($tables)) {
+ // 支持别名定义
+ $array = array();
+ foreach ($tables as $table => $alias) {
+ if (!is_numeric($table)) {
+ $array[] = $this->parseKey($table) . ' ' . $this->parseKey($alias);
+ } else {
+ $array[] = $this->parseKey($alias);
+ }
+
+ }
+ $tables = $array;
+ } elseif (is_string($tables)) {
+ $tables = array_map(array($this, 'parseKey'), explode(',', $tables));
+ }
+ return implode(',', $tables);
+ }
+
+ /**
+ * where分析
+ * @access protected
+ * @param mixed $where
+ * @return string
+ */
+ protected function parseWhere($where)
+ {
+ $whereStr = '';
+ if (is_string($where)) {
+ // 直接使用字符串条件
+ $whereStr = $where;
+ } else {
+ // 使用数组表达式
+ $operate = isset($where['_logic']) ? strtoupper($where['_logic']) : '';
+ if (in_array($operate, array('AND', 'OR', 'XOR'))) {
+ // 定义逻辑运算规则 例如 OR XOR AND NOT
+ $operate = ' ' . $operate . ' ';
+ unset($where['_logic']);
+ } else {
+ // 默认进行 AND 运算
+ $operate = ' AND ';
+ }
+ foreach ($where as $key => $val) {
+ if (is_numeric($key)) {
+ $key = '_complex';
+ }
+ if (0 === strpos($key, '_')) {
+ // 解析特殊条件表达式
+ $whereStr .= $this->parseThinkWhere($key, $val);
+ } else {
+ // 查询字段的安全过滤
+ // if(!preg_match('/^[A-Z_\|\&\-.a-z0-9\(\)\,]+$/',trim($key))){
+ // E(L('_EXPRESS_ERROR_').':'.$key);
+ // }
+ // 多条件支持
+ $multi = is_array($val) && isset($val['_multi']);
+ $key = trim($key);
+ if (strpos($key, '|')) {
+ // 支持 name|title|nickname 方式定义查询字段
+ $array = explode('|', $key);
+ $str = array();
+ foreach ($array as $m => $k) {
+ $v = $multi ? $val[$m] : $val;
+ $str[] = $this->parseWhereItem($this->parseKey($k), $v);
+ }
+ $whereStr .= '( ' . implode(' OR ', $str) . ' )';
+ } elseif (strpos($key, '&')) {
+ $array = explode('&', $key);
+ $str = array();
+ foreach ($array as $m => $k) {
+ $v = $multi ? $val[$m] : $val;
+ $str[] = '(' . $this->parseWhereItem($this->parseKey($k), $v) . ')';
+ }
+ $whereStr .= '( ' . implode(' AND ', $str) . ' )';
+ } else {
+ $whereStr .= $this->parseWhereItem($this->parseKey($key), $val);
+ }
+ }
+ $whereStr .= $operate;
+ }
+ $whereStr = substr($whereStr, 0, -strlen($operate));
+ }
+ return empty($whereStr) ? '' : ' WHERE ' . $whereStr;
+ }
+
+ // where子单元分析
+ protected function parseWhereItem($key, $val)
+ {
+ $whereStr = '';
+ if (is_array($val)) {
+ if (is_string($val[0])) {
+ $exp = strtolower($val[0]);
+ if (preg_match('/^(eq|neq|gt|egt|lt|elt)$/', $exp)) {
+ // 比较运算
+ $whereStr .= $key . ' ' . $this->exp[$exp] . ' ' . $this->parseValue($val[1]);
+ } elseif (preg_match('/^(notlike|like)$/', $exp)) {
+ // 模糊查找
+ if (is_array($val[1])) {
+ $likeLogic = isset($val[2]) ? strtoupper($val[2]) : 'OR';
+ if (in_array($likeLogic, array('AND', 'OR', 'XOR'))) {
+ $like = array();
+ foreach ($val[1] as $item) {
+ $like[] = $key . ' ' . $this->exp[$exp] . ' ' . $this->parseValue($item);
+ }
+ $whereStr .= '(' . implode(' ' . $likeLogic . ' ', $like) . ')';
+ }
+ } else {
+ $whereStr .= $key . ' ' . $this->exp[$exp] . ' ' . $this->parseValue($val[1]);
+ }
+ } elseif ('bind' == $exp) {
+ // 使用表达式
+ $whereStr .= $key . ' = :' . $val[1];
+ } elseif ('exp' == $exp) {
+ // 使用表达式
+ $whereStr .= $key . ' ' . $val[1];
+ } elseif (preg_match('/^(notin|not in|in)$/', $exp)) {
+ // IN 运算
+ if (isset($val[2]) && 'exp' == $val[2]) {
+ $whereStr .= $key . ' ' . $this->exp[$exp] . ' ' . $val[1];
+ } else {
+ if (is_string($val[1])) {
+ $val[1] = explode(',', $val[1]);
+ }
+ $zone = implode(',', $this->parseValue($val[1]));
+ $whereStr .= $key . ' ' . $this->exp[$exp] . ' (' . $zone . ')';
+ }
+ } elseif (preg_match('/^(notbetween|not between|between)$/', $exp)) {
+ // BETWEEN运算
+ $data = is_string($val[1]) ? explode(',', $val[1]) : $val[1];
+ $whereStr .= $key . ' ' . $this->exp[$exp] . ' ' . $this->parseValue($data[0]) . ' AND ' . $this->parseValue($data[1]);
+ } else {
+ E(L('_EXPRESS_ERROR_') . ':' . $val[0]);
+ }
+ } else {
+ $count = count($val);
+ $rule = isset($val[$count - 1]) ? (is_array($val[$count - 1]) ? strtoupper($val[$count - 1][0]) : strtoupper($val[$count - 1])) : '';
+ if (in_array($rule, array('AND', 'OR', 'XOR'))) {
+ $count = $count - 1;
+ } else {
+ $rule = 'AND';
+ }
+ for ($i = 0; $i < $count; $i++) {
+ $data = is_array($val[$i]) ? $val[$i][1] : $val[$i];
+ if ('exp' == strtolower($val[$i][0])) {
+ $whereStr .= $key . ' ' . $data . ' ' . $rule . ' ';
+ } else {
+ $whereStr .= $this->parseWhereItem($key, $val[$i]) . ' ' . $rule . ' ';
+ }
+ }
+ $whereStr = '( ' . substr($whereStr, 0, -4) . ' )';
+ }
+ } else {
+ //对字符串类型字段采用模糊匹配
+ $likeFields = $this->config['db_like_fields'];
+ if ($likeFields && preg_match('/^(' . $likeFields . ')$/i', $key)) {
+ $whereStr .= $key . ' LIKE ' . $this->parseValue('%' . $val . '%');
+ } else {
+ $whereStr .= $key . ' = ' . $this->parseValue($val);
+ }
+ }
+ return $whereStr;
+ }
+
+ /**
+ * 特殊条件分析
+ * @access protected
+ * @param string $key
+ * @param mixed $val
+ * @return string
+ */
+ protected function parseThinkWhere($key, $val)
+ {
+ $whereStr = '';
+ switch ($key) {
+ case '_string':
+ // 字符串模式查询条件
+ $whereStr = $val;
+ break;
+ case '_complex':
+ // 复合查询条件
+ $whereStr = substr($this->parseWhere($val), 6);
+ break;
+ case '_query':
+ // 字符串模式查询条件
+ parse_str($val, $where);
+ if (isset($where['_logic'])) {
+ $op = ' ' . strtoupper($where['_logic']) . ' ';
+ unset($where['_logic']);
+ } else {
+ $op = ' AND ';
+ }
+ $array = array();
+ foreach ($where as $field => $data) {
+ $array[] = $this->parseKey($field) . ' = ' . $this->parseValue($data);
+ }
+
+ $whereStr = implode($op, $array);
+ break;
+ }
+ return '( ' . $whereStr . ' )';
+ }
+
+ /**
+ * limit分析
+ * @access protected
+ * @param mixed $lmit
+ * @return string
+ */
+ protected function parseLimit($limit)
+ {
+ return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : '';
+ }
+
+ /**
+ * join分析
+ * @access protected
+ * @param mixed $join
+ * @return string
+ */
+ protected function parseJoin($join)
+ {
+ $joinStr = '';
+ if (!empty($join)) {
+ $joinStr = ' ' . implode(' ', $join) . ' ';
+ }
+ return $joinStr;
+ }
+
+ /**
+ * order分析
+ * @access protected
+ * @param mixed $order
+ * @return string
+ */
+ protected function parseOrder($order)
+ {
+ if (empty($order)) {
+ return '';
+ }
+ $array = array();
+ if (is_array($order)) {
+ foreach ($order as $key => $val) {
+ if (is_numeric($key)) {
+ if (false === strpos($val, '(')) {
+ $array[] = $this->parseKey($val);
+ }
+ } else {
+ $sort = in_array(strtolower($val), array('asc', 'desc')) ? ' ' . $val : '';
+ $array[] = $this->parseKey($key) . $sort;
+ }
+ }
+ } elseif ('[RAND]' == $order) {
+ // 随机排序
+ $array[] = $this->parseRand();
+ } else {
+ foreach (explode(',', $order) as $val) {
+ if (preg_match('/\s+(ASC|DESC)$/i', rtrim($val), $match, PREG_OFFSET_CAPTURE)) {
+ $array[] = $this->parseKey(ltrim(substr($val, 0, $match[0][1]))) . ' ' . $match[1][0];
+ } elseif (false === strpos($val, '(')) {
+ $array[] = $this->parseKey($val);
+ }
+ }
+ }
+ $order = implode(',', $array);
+ return !empty($order) ? ' ORDER BY ' . $order : '';
+ }
+
+ /**
+ * group分析
+ * @access protected
+ * @param mixed $group
+ * @return string
+ */
+ protected function parseGroup($group)
+ {
+ return !empty($group) ? ' GROUP BY ' . $group : '';
+ }
+
+ /**
+ * having分析
+ * @access protected
+ * @param string $having
+ * @return string
+ */
+ protected function parseHaving($having)
+ {
+ return !empty($having) ? ' HAVING ' . $having : '';
+ }
+
+ /**
+ * comment分析
+ * @access protected
+ * @param string $comment
+ * @return string
+ */
+ protected function parseComment($comment)
+ {
+ return !empty($comment) ? ' /* ' . $comment . ' */' : '';
+ }
+
+ /**
+ * distinct分析
+ * @access protected
+ * @param mixed $distinct
+ * @return string
+ */
+ protected function parseDistinct($distinct)
+ {
+ return !empty($distinct) ? ' DISTINCT ' : '';
+ }
+
+ /**
+ * union分析
+ * @access protected
+ * @param mixed $union
+ * @return string
+ */
+ protected function parseUnion($union)
+ {
+ if (empty($union)) {
+ return '';
+ }
+
+ if (isset($union['_all'])) {
+ $str = 'UNION ALL ';
+ unset($union['_all']);
+ } else {
+ $str = 'UNION ';
+ }
+ foreach ($union as $u) {
+ $sql[] = $str . (is_array($u) ? $this->buildSelectSql($u) : $u);
+ }
+ return implode(' ', $sql);
+ }
+
+ /**
+ * 参数绑定分析
+ * @access protected
+ * @param array $bind
+ * @return array
+ */
+ protected function parseBind($bind)
+ {
+ $this->bind = array_merge($this->bind, $bind);
+ }
+
+ /**
+ * index分析,可在操作链中指定需要强制使用的索引
+ * @access protected
+ * @param mixed $index
+ * @return string
+ */
+ protected function parseForce($index)
+ {
+ if (empty($index)) {
+ return '';
+ }
+
+ if (is_array($index)) {
+ $index = join(",", $index);
+ }
+
+ return sprintf(" FORCE INDEX ( %s ) ", $index);
+ }
+
+ /**
+ * ON DUPLICATE KEY UPDATE 分析
+ * @access protected
+ * @param mixed $duplicate
+ * @return string
+ */
+ protected function parseDuplicate($duplicate)
+ {
+ return '';
+ }
+
+ /**
+ * 插入记录
+ * @access public
+ * @param mixed $data 数据
+ * @param array $options 参数表达式
+ * @param boolean $replace 是否replace
+ * @return false | integer
+ */
+ public function insert($data, $options = array(), $replace = false)
+ {
+ $values = $fields = array();
+ $this->model = $options['model'];
+ $this->parseBind(!empty($options['bind']) ? $options['bind'] : array());
+ foreach ($data as $key => $val) {
+ if (isset($val[0]) && 'exp' == $val[0]) {
+ $fields[] = $this->parseKey($key);
+ $values[] = $val[1];
+ } elseif (is_null($val)) {
+ $fields[] = $this->parseKey($key);
+ $values[] = 'NULL';
+ } elseif (is_scalar($val)) {
+ // 过滤非标量数据
+ $fields[] = $this->parseKey($key);
+ if (0 === strpos($val, ':') && in_array($val, array_keys($this->bind))) {
+ $values[] = $val;
+ } else {
+ $name = count($this->bind);
+ $values[] = ':' . $key . '_' . $name;
+ $this->bindParam($key . '_' . $name, $val);
+ }
+ }
+ }
+ // 兼容数字传入方式
+ $replace = (is_numeric($replace) && $replace > 0) ? true : $replace;
+ $sql = (true === $replace ? 'REPLACE' : 'INSERT') . ' INTO ' . $this->parseTable($options['table']) . ' (' . implode(',', $fields) . ') VALUES (' . implode(',', $values) . ')' . $this->parseDuplicate($replace);
+ $sql .= $this->parseComment(!empty($options['comment']) ? $options['comment'] : '');
+ return $this->execute($sql, !empty($options['fetch_sql']) ? true : false);
+ }
+
+ /**
+ * 批量插入记录
+ * @access public
+ * @param mixed $dataSet 数据集
+ * @param array $options 参数表达式
+ * @param boolean $replace 是否replace
+ * @return false | integer
+ */
+ public function insertAll($dataSet, $options = array(), $replace = false)
+ {
+ $values = array();
+ $this->model = $options['model'];
+ if (!is_array($dataSet[0])) {
+ return false;
+ }
+
+ $this->parseBind(!empty($options['bind']) ? $options['bind'] : array());
+ $fields = array_map(array($this, 'parseKey'), array_keys($dataSet[0]));
+ foreach ($dataSet as $data) {
+ $value = array();
+ foreach ($data as $key => $val) {
+ if (is_array($val) && 'exp' == $val[0]) {
+ $value[] = $val[1];
+ } elseif (is_null($val)) {
+ $value[] = 'NULL';
+ } elseif (is_scalar($val)) {
+ if (0 === strpos($val, ':') && in_array($val, array_keys($this->bind))) {
+ $value[] = $val;
+ } else {
+ $name = count($this->bind);
+ $value[] = ':' . $key . '_' . $name;
+ $this->bindParam($key . '_' . $name, $val);
+ }
+ }
+ }
+ $values[] = 'SELECT ' . implode(',', $value);
+ }
+ $sql = 'INSERT INTO ' . $this->parseTable($options['table']) . ' (' . implode(',', $fields) . ') ' . implode(' UNION ALL ', $values);
+ $sql .= $this->parseComment(!empty($options['comment']) ? $options['comment'] : '');
+ return $this->execute($sql, !empty($options['fetch_sql']) ? true : false);
+ }
+
+ /**
+ * 通过Select方式插入记录
+ * @access public
+ * @param string $fields 要插入的数据表字段名
+ * @param string $table 要插入的数据表名
+ * @param array $option 查询数据参数
+ * @return false | integer
+ */
+ public function selectInsert($fields, $table, $options = array())
+ {
+ $this->model = $options['model'];
+ $this->parseBind(!empty($options['bind']) ? $options['bind'] : array());
+ if (is_string($fields)) {
+ $fields = explode(',', $fields);
+ }
+
+ $fields = array_map(array($this, 'parseKey'), $fields);
+ $sql = 'INSERT INTO ' . $this->parseTable($table) . ' (' . implode(',', $fields) . ') ';
+ $sql .= $this->buildSelectSql($options);
+ return $this->execute($sql, !empty($options['fetch_sql']) ? true : false);
+ }
+
+ /**
+ * 更新记录
+ * @access public
+ * @param mixed $data 数据
+ * @param array $options 表达式
+ * @return false | integer
+ */
+ public function update($data, $options)
+ {
+ $this->model = $options['model'];
+ $this->parseBind(!empty($options['bind']) ? $options['bind'] : array());
+ $table = $this->parseTable($options['table']);
+ $sql = 'UPDATE ' . $table . $this->parseSet($data);
+ if (strpos($table, ',')) {
+ // 多表更新支持JOIN操作
+ $sql .= $this->parseJoin(!empty($options['join']) ? $options['join'] : '');
+ }
+ $sql .= $this->parseWhere(!empty($options['where']) ? $options['where'] : '');
+ if (!strpos($table, ',')) {
+ // 单表更新支持order和lmit
+ $sql .= $this->parseOrder(!empty($options['order']) ? $options['order'] : '')
+ . $this->parseLimit(!empty($options['limit']) ? $options['limit'] : '');
+ }
+ $sql .= $this->parseComment(!empty($options['comment']) ? $options['comment'] : '');
+ return $this->execute($sql, !empty($options['fetch_sql']) ? true : false);
+ }
+
+ /**
+ * 删除记录
+ * @access public
+ * @param array $options 表达式
+ * @return false | integer
+ */
+ public function delete($options = array())
+ {
+ $this->model = $options['model'];
+ $this->parseBind(!empty($options['bind']) ? $options['bind'] : array());
+ $table = $this->parseTable($options['table']);
+ $sql = 'DELETE FROM ' . $table;
+ if (strpos($table, ',')) {
+ // 多表删除支持USING和JOIN操作
+ if (!empty($options['using'])) {
+ $sql .= ' USING ' . $this->parseTable($options['using']) . ' ';
+ }
+ $sql .= $this->parseJoin(!empty($options['join']) ? $options['join'] : '');
+ }
+ $sql .= $this->parseWhere(!empty($options['where']) ? $options['where'] : '');
+ if (!strpos($table, ',')) {
+ // 单表删除支持order和limit
+ $sql .= $this->parseOrder(!empty($options['order']) ? $options['order'] : '')
+ . $this->parseLimit(!empty($options['limit']) ? $options['limit'] : '');
+ }
+ $sql .= $this->parseComment(!empty($options['comment']) ? $options['comment'] : '');
+ return $this->execute($sql, !empty($options['fetch_sql']) ? true : false);
+ }
+
+ /**
+ * 查找记录
+ * @access public
+ * @param array $options 表达式
+ * @return mixed
+ */
+ public function select($options = array())
+ {
+ $this->model = $options['model'];
+ $this->parseBind(!empty($options['bind']) ? $options['bind'] : array());
+ $sql = $this->buildSelectSql($options);
+ $result = $this->query($sql, !empty($options['fetch_sql']) ? true : false, !empty($options['master']) ? true : false);
+ return $result;
+ }
+
+ /**
+ * 生成查询SQL
+ * @access public
+ * @param array $options 表达式
+ * @return string
+ */
+ public function buildSelectSql($options = array())
+ {
+ if (isset($options['page'])) {
+ // 根据页数计算limit
+ list($page, $listRows) = $options['page'];
+ $page = $page > 0 ? $page : 1;
+ $listRows = $listRows > 0 ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20);
+ $offset = $listRows * ($page - 1);
+ $options['limit'] = $offset . ',' . $listRows;
+ }
+ $sql = $this->parseSql($this->selectSql, $options);
+ return $sql;
+ }
+
+ /**
+ * 替换SQL语句中表达式
+ * @access public
+ * @param array $options 表达式
+ * @return string
+ */
+ public function parseSql($sql, $options = array())
+ {
+ $sql = str_replace(
+ array('%TABLE%', '%DISTINCT%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'),
+ array(
+ $this->parseTable($options['table']),
+ $this->parseDistinct(isset($options['distinct']) ? $options['distinct'] : false),
+ $this->parseField(!empty($options['field']) ? $options['field'] : '*'),
+ $this->parseJoin(!empty($options['join']) ? $options['join'] : ''),
+ $this->parseWhere(!empty($options['where']) ? $options['where'] : ''),
+ $this->parseGroup(!empty($options['group']) ? $options['group'] : ''),
+ $this->parseHaving(!empty($options['having']) ? $options['having'] : ''),
+ $this->parseOrder(!empty($options['order']) ? $options['order'] : ''),
+ $this->parseLimit(!empty($options['limit']) ? $options['limit'] : ''),
+ $this->parseUnion(!empty($options['union']) ? $options['union'] : ''),
+ $this->parseLock(isset($options['lock']) ? $options['lock'] : false),
+ $this->parseComment(!empty($options['comment']) ? $options['comment'] : ''),
+ $this->parseForce(!empty($options['force']) ? $options['force'] : ''),
+ ), $sql);
+ return $sql;
+ }
+
+ /**
+ * 获取最近一次查询的sql语句
+ * @param string $model 模型名
+ * @access public
+ * @return string
+ */
+ public function getLastSql($model = '')
+ {
+ return $model ? $this->modelSql[$model] : $this->queryStr;
+ }
+
+ /**
+ * 获取最近插入的ID
+ * @access public
+ * @return string
+ */
+ public function getLastInsID()
+ {
+ return $this->lastInsID;
+ }
+
+ /**
+ * 获取最近的错误信息
+ * @access public
+ * @return string
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * SQL指令安全过滤
+ * @access public
+ * @param string $str SQL字符串
+ * @return string
+ */
+ public function escapeString($str)
+ {
+ return addslashes($str);
+ }
+
+ /**
+ * 设置当前操作模型
+ * @access public
+ * @param string $model 模型名
+ * @return void
+ */
+ public function setModel($model)
+ {
+ $this->model = $model;
+ }
+
+ /**
+ * 数据库调试 记录当前SQL
+ * @access protected
+ * @param boolean $start 调试开始标记 true 开始 false 结束
+ */
+ protected function debug($start)
+ {
+ if ($this->config['debug']) {
+ // 开启数据库调试模式
+ if ($start) {
+ G('queryStartTime');
+ } else {
+ $this->modelSql[$this->model] = $this->queryStr;
+ //$this->model = '_think_';
+ // 记录操作结束时间
+ G('queryEndTime');
+ trace($this->queryStr . ' [ RunTime:' . G('queryStartTime', 'queryEndTime') . 's ]', '', 'SQL');
+ }
+ }
+ }
+
+ /**
+ * 初始化数据库连接
+ * @access protected
+ * @param boolean $master 主服务器
+ * @return void
+ */
+ protected function initConnect($master = true)
+ {
+ // 开启事物时用同一个连接进行操作
+ if ($this->transPDO) {
+ return $this->transPDO;
+ }
+
+ if (!empty($this->config['deploy']))
+ // 采用分布式数据库
+ {
+ $this->_linkID = $this->multiConnect($master);
+ } else
+ // 默认单数据库
+ if (!$this->_linkID) {
+ $this->_linkID = $this->connect();
+ }
+
+ }
+
+ /**
+ * 连接分布式服务器
+ * @access protected
+ * @param boolean $master 主服务器
+ * @return void
+ */
+ protected function multiConnect($master = false)
+ {
+ // 分布式数据库配置解析
+ $_config['username'] = explode(',', $this->config['username']);
+ $_config['password'] = explode(',', $this->config['password']);
+ $_config['hostname'] = explode(',', $this->config['hostname']);
+ $_config['hostport'] = explode(',', $this->config['hostport']);
+ $_config['database'] = explode(',', $this->config['database']);
+ $_config['dsn'] = explode(',', $this->config['dsn']);
+ $_config['charset'] = explode(',', $this->config['charset']);
+
+ $m = floor(mt_rand(0, $this->config['master_num'] - 1));
+ // 数据库读写是否分离
+ if ($this->config['rw_separate']) {
+ // 主从式采用读写分离
+ if ($master)
+ // 主服务器写入
+ {
+ $r = $m;
+ } else {
+ if (is_numeric($this->config['slave_no'])) {
+ // 指定服务器读
+ $r = $this->config['slave_no'];
+ } else {
+ // 读操作连接从服务器
+ $r = floor(mt_rand($this->config['master_num'], count($_config['hostname']) - 1)); // 每次随机连接的数据库
+ }
+ }
+ } else {
+ // 读写操作不区分服务器
+ $r = floor(mt_rand(0, count($_config['hostname']) - 1)); // 每次随机连接的数据库
+ }
+
+ if ($m != $r) {
+ $db_master = array(
+ 'username' => isset($_config['username'][$m]) ? $_config['username'][$m] : $_config['username'][0],
+ 'password' => isset($_config['password'][$m]) ? $_config['password'][$m] : $_config['password'][0],
+ 'hostname' => isset($_config['hostname'][$m]) ? $_config['hostname'][$m] : $_config['hostname'][0],
+ 'hostport' => isset($_config['hostport'][$m]) ? $_config['hostport'][$m] : $_config['hostport'][0],
+ 'database' => isset($_config['database'][$m]) ? $_config['database'][$m] : $_config['database'][0],
+ 'dsn' => isset($_config['dsn'][$m]) ? $_config['dsn'][$m] : $_config['dsn'][0],
+ 'charset' => isset($_config['charset'][$m]) ? $_config['charset'][$m] : $_config['charset'][0],
+ );
+ }
+ $db_config = array(
+ 'username' => isset($_config['username'][$r]) ? $_config['username'][$r] : $_config['username'][0],
+ 'password' => isset($_config['password'][$r]) ? $_config['password'][$r] : $_config['password'][0],
+ 'hostname' => isset($_config['hostname'][$r]) ? $_config['hostname'][$r] : $_config['hostname'][0],
+ 'hostport' => isset($_config['hostport'][$r]) ? $_config['hostport'][$r] : $_config['hostport'][0],
+ 'database' => isset($_config['database'][$r]) ? $_config['database'][$r] : $_config['database'][0],
+ 'dsn' => isset($_config['dsn'][$r]) ? $_config['dsn'][$r] : $_config['dsn'][0],
+ 'charset' => isset($_config['charset'][$r]) ? $_config['charset'][$r] : $_config['charset'][0],
+ );
+ return $this->connect($db_config, $r, $r == $m ? false : $db_master);
+ }
+
+ /**
+ * 析构方法
+ * @access public
+ */
+ public function __destruct()
+ {
+ // 释放查询
+ if ($this->PDOStatement) {
+ $this->free();
+ }
+ // 关闭连接
+ $this->close();
+ }
+}
diff --git a/Framework/Library/Think/Db/Driver/Firebird.class.php b/Framework/Library/Think/Db/Driver/Firebird.class.php
new file mode 100644
index 00000000..3a2987b0
--- /dev/null
+++ b/Framework/Library/Think/Db/Driver/Firebird.class.php
@@ -0,0 +1,176 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Db\Driver;
+
+use Think\Db\Driver;
+
+/**
+ * Firebird数据库驱动
+ */
+class Firebird extends Driver
+{
+ protected $selectSql = 'SELECT %LIMIT% %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%';
+
+ /**
+ * 解析pdo连接的dsn信息
+ * @access public
+ * @param array $config 连接信息
+ * @return string
+ */
+ protected function parseDsn($config)
+ {
+ $dsn = 'firebird:dbname=' . $config['hostname'] . '/' . ($config['hostport'] ?: 3050) . ':' . $config['database'];
+ return $dsn;
+ }
+
+ /**
+ * 执行语句
+ * @access public
+ * @param string $str sql指令
+ * @param boolean $fetchSql 不执行只是获取SQL
+ * @return mixed
+ */
+ public function execute($str, $fetchSql = false)
+ {
+ $this->initConnect(true);
+ if (!$this->_linkID) {
+ return false;
+ }
+
+ $this->queryStr = $str;
+ if (!empty($this->bind)) {
+ $that = $this;
+ $this->queryStr = strtr($this->queryStr, array_map(function ($val) use ($that) {return '\'' . $that->escapeString($val) . '\'';}, $this->bind));
+ }
+ if ($fetchSql) {
+ return $this->queryStr;
+ }
+ //释放前次的查询结果
+ if (!empty($this->PDOStatement)) {
+ $this->free();
+ }
+
+ $this->executeTimes++;
+ N('db_write', 1); // 兼容代码
+ // 记录开始执行时间
+ $this->debug(true);
+ $this->PDOStatement = $this->_linkID->prepare($str);
+ if (false === $this->PDOStatement) {
+ E($this->error());
+ }
+ foreach ($this->bind as $key => $val) {
+ if (is_array($val)) {
+ $this->PDOStatement->bindValue($key, $val[0], $val[1]);
+ } else {
+ $this->PDOStatement->bindValue($key, $val);
+ }
+ }
+ $this->bind = array();
+ $result = $this->PDOStatement->execute();
+ $this->debug(false);
+ if (false === $result) {
+ $this->error();
+ return false;
+ } else {
+ $this->numRows = $this->PDOStatement->rowCount();
+ return $this->numRows;
+ }
+ }
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ */
+ public function getFields($tableName)
+ {
+ $this->initConnect(true);
+ list($tableName) = explode(' ', $tableName);
+ $sql = 'SELECT RF.RDB$FIELD_NAME AS FIELD,RF.RDB$DEFAULT_VALUE AS DEFAULT1,RF.RDB$NULL_FLAG AS NULL1,TRIM(T.RDB$TYPE_NAME) || \'(\' || F.RDB$FIELD_LENGTH || \')\' as TYPE FROM RDB$RELATION_FIELDS RF LEFT JOIN RDB$FIELDS F ON (F.RDB$FIELD_NAME = RF.RDB$FIELD_SOURCE) LEFT JOIN RDB$TYPES T ON (T.RDB$TYPE = F.RDB$FIELD_TYPE) WHERE RDB$RELATION_NAME=UPPER(\'' . $tableName . '\') AND T.RDB$FIELD_NAME = \'RDB$FIELD_TYPE\' ORDER By RDB$FIELD_POSITION';
+ $result = $this->query($sql);
+ $info = array();
+ if ($result) {
+ foreach ($result as $key => $val) {
+ $info[trim($val['field'])] = array(
+ 'name' => trim($val['field']),
+ 'type' => $val['type'],
+ 'notnull' => (bool) (1 == $val['null1']), // 1表示不为Null
+ 'default' => $val['default1'],
+ 'primary' => false,
+ 'autoinc' => false,
+ );
+ }
+ }
+ //获取主键
+ $sql = 'select b.rdb$field_name as field_name from rdb$relation_constraints a join rdb$index_segments b on a.rdb$index_name=b.rdb$index_name where a.rdb$constraint_type=\'PRIMARY KEY\' and a.rdb$relation_name=UPPER(\'' . $tableName . '\')';
+ $rs_temp = $this->query($sql);
+ foreach ($rs_temp as $row) {
+ $info[trim($row['field_name'])]['primary'] = true;
+ }
+ return $info;
+ }
+
+ /**
+ * 取得数据库的表信息
+ * @access public
+ */
+ public function getTables($dbName = '')
+ {
+ $sql = 'SELECT DISTINCT RDB$RELATION_NAME FROM RDB$RELATION_FIELDS WHERE RDB$SYSTEM_FLAG=0';
+ $result = $this->query($sql);
+ $info = array();
+ foreach ($result as $key => $val) {
+ $info[$key] = trim(current($val));
+ }
+ return $info;
+ }
+
+ /**
+ * SQL指令安全过滤
+ * @access public
+ * @param string $str SQL指令
+ * @return string
+ */
+ public function escapeString($str)
+ {
+ return str_replace("'", "''", $str);
+ }
+
+ /**
+ * limit
+ * @access public
+ * @param $limit limit表达式
+ * @return string
+ */
+ public function parseLimit($limit)
+ {
+ $limitStr = '';
+ if (!empty($limit)) {
+ $limit = explode(',', $limit);
+ if (count($limit) > 1) {
+ $limitStr = ' FIRST ' . $limit[1] . ' SKIP ' . $limit[0] . ' ';
+ } else {
+ $limitStr = ' FIRST ' . $limit[0] . ' ';
+ }
+ }
+ return $limitStr;
+ }
+
+ /**
+ * 随机排序
+ * @access protected
+ * @return string
+ */
+ protected function parseRand()
+ {
+ return 'rand()';
+ }
+
+}
diff --git a/Framework/Library/Think/Db/Driver/Mongo.class.php b/Framework/Library/Think/Db/Driver/Mongo.class.php
new file mode 100644
index 00000000..29909711
--- /dev/null
+++ b/Framework/Library/Think/Db/Driver/Mongo.class.php
@@ -0,0 +1,887 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think\Db\Driver;
+
+use Think\Db\Driver;
+
+/**
+ * Mongo数据库驱动
+ */
+class Mongo extends Driver
+{
+
+ protected $_mongo = null; // MongoDb Object
+ protected $_collection = null; // MongoCollection Object
+ protected $_dbName = ''; // dbName
+ protected $_collectionName = ''; // collectionName
+ protected $_cursor = null; // MongoCursor Object
+ protected $comparison = array('neq' => 'ne', 'ne' => 'ne', 'gt' => 'gt', 'egt' => 'gte', 'gte' => 'gte', 'lt' => 'lt', 'elt' => 'lte', 'lte' => 'lte', 'in' => 'in', 'not in' => 'nin', 'nin' => 'nin');
+
+ /**
+ * 架构函数 读取数据库配置信息
+ * @access public
+ * @param array $config 数据库配置数组
+ */
+ public function __construct($config = '')
+ {
+ if (!class_exists('mongoClient')) {
+ E(L('_NOT_SUPPORT_') . ':Mongo');
+ }
+ if (!empty($config)) {
+ $this->config = array_merge($this->config, $config);
+ if (empty($this->config['params'])) {
+ $this->config['params'] = array();
+ }
+ }
+ }
+
+ /**
+ * 连接数据库方法
+ * @access public
+ */
+ public function connect($config = '', $linkNum = 0)
+ {
+ if (!isset($this->linkID[$linkNum])) {
+ if (empty($config)) {
+ $config = $this->config;
+ }
+
+ $host = 'mongodb://' . ($config['username'] ? "{$config['username']}" : '') . ($config['password'] ? ":{$config['password']}@" : '') . $config['hostname'] . ($config['hostport'] ? ":{$config['hostport']}" : '') . '/' . ($config['database'] ? "{$config['database']}" : '');
+ try {
+ $this->linkID[$linkNum] = new \mongoClient($host, $this->config['params']);
+ } catch (\MongoConnectionException $e) {
+ E($e->getmessage());
+ }
+ }
+ return $this->linkID[$linkNum];
+ }
+
+ /**
+ * 切换当前操作的Db和Collection
+ * @access public
+ * @param string $collection collection
+ * @param string $db db
+ * @param boolean $master 是否主服务器
+ * @return void
+ */
+ public function switchCollection($collection, $db = '', $master = true)
+ {
+ // 当前没有连接 则首先进行数据库连接
+ if (!$this->_linkID) {
+ $this->initConnect($master);
+ }
+
+ try {
+ if (!empty($db)) {
+ // 传人Db则切换数据库
+ // 当前MongoDb对象
+ $this->_dbName = $db;
+ $this->_mongo = $this->_linkID->selectDb($db);
+ }
+ // 当前MongoCollection对象
+ if ($this->config['debug']) {
+ $this->queryStr = $this->_dbName . '.getCollection(' . $collection . ')';
+ }
+ if ($this->_collectionName != $collection) {
+ $this->queryTimes++;
+ N('db_query', 1); // 兼容代码
+ $this->debug(true);
+ $this->_collection = $this->_mongo->selectCollection($collection);
+ $this->debug(false);
+ $this->_collectionName = $collection; // 记录当前Collection名称
+ }
+ } catch (MongoException $e) {
+ E($e->getMessage());
+ }
+ }
+
+ /**
+ * 释放查询结果
+ * @access public
+ */
+ public function free()
+ {
+ $this->_cursor = null;
+ }
+
+ /**
+ * 执行命令
+ * @access public
+ * @param array $command 指令
+ * @return array
+ */
+ public function command($command = array(), $options = array())
+ {
+ $cache = isset($options['cache']) ? $options['cache'] : false;
+ if ($cache) {
+ // 查询缓存检测
+ $key = is_string($cache['key']) ? $cache['key'] : md5(serialize($command));
+ $value = S($key, '', '', $cache['type']);
+ if (false !== $value) {
+ return $value;
+ }
+ }
+ N('db_write', 1); // 兼容代码
+ $this->executeTimes++;
+ try {
+ if ($this->config['debug']) {
+ $this->queryStr = $this->_dbName . '.' . $this->_collectionName . '.runCommand(';
+ $this->queryStr .= json_encode($command);
+ $this->queryStr .= ')';
+ }
+ $this->debug(true);
+ $result = $this->_mongo->command($command);
+ $this->debug(false);
+
+ if ($cache && $result['ok']) {
+ // 查询缓存写入
+ S($key, $result, $cache['expire'], $cache['type']);
+ }
+ return $result;
+ } catch (\MongoCursorException $e) {
+ E($e->getMessage());
+ }
+ }
+
+ /**
+ * 执行语句
+ * @access public
+ * @param string $code sql指令
+ * @param array $args 参数
+ * @return mixed
+ */
+ public function execute($code, $args = array())
+ {
+ $this->executeTimes++;
+ N('db_write', 1); // 兼容代码
+ $this->debug(true);
+ $this->queryStr = 'execute:' . $code;
+ $result = $this->_mongo->execute($code, $args);
+ $this->debug(false);
+ if ($result['ok']) {
+ return $result['retval'];
+ } else {
+ E($result['errmsg']);
+ }
+ }
+
+ /**
+ * 关闭数据库
+ * @access public
+ */
+ public function close()
+ {
+ if ($this->_linkID) {
+ $this->_linkID->close();
+ $this->_linkID = null;
+ $this->_mongo = null;
+ $this->_collection = null;
+ $this->_cursor = null;
+ }
+ }
+
+ /**
+ * 数据库错误信息
+ * @access public
+ * @return string
+ */
+ public function error()
+ {
+ $this->error = $this->_mongo->lastError();
+ trace($this->error, '', 'ERR');
+ return $this->error;
+ }
+
+ /**
+ * 插入记录
+ * @access public
+ * @param mixed $data 数据
+ * @param array $options 参数表达式
+ * @param boolean $replace 是否replace
+ * @return false | integer
+ */
+ public function insert($data, $options = array(), $replace = false)
+ {
+ if (isset($options['table'])) {
+ $this->switchCollection($options['table']);
+ }
+ $this->model = $options['model'];
+ $this->executeTimes++;
+ N('db_write', 1); // 兼容代码
+ if ($this->config['debug']) {
+ $this->queryStr = $this->_dbName . '.' . $this->_collectionName . '.insert(';
+ $this->queryStr .= $data ? json_encode($data) : '{}';
+ $this->queryStr .= ')';
+ }
+ try {
+ $this->debug(true);
+ $result = $replace ? $this->_collection->save($data) : $this->_collection->insert($data);
+ $this->debug(false);
+ if ($result) {
+ $_id = $data['_id'];
+ if (is_object($_id)) {
+ $_id = $_id->__toString();
+ }
+ $this->lastInsID = $_id;
+ }
+ return $result;
+ } catch (\MongoCursorException $e) {
+ E($e->getMessage());
+ }
+ }
+
+ /**
+ * 插入多条记录
+ * @access public
+ * @param array $dataList 数据
+ * @param array $options 参数表达式
+ * @return bool
+ */
+ public function insertAll($dataList, $options = array())
+ {
+ if (isset($options['table'])) {
+ $this->switchCollection($options['table']);
+ }
+ $this->model = $options['model'];
+ $this->executeTimes++;
+ N('db_write', 1); // 兼容代码
+ try {
+ $this->debug(true);
+ $result = $this->_collection->batchInsert($dataList);
+ $this->debug(false);
+ return $result;
+ } catch (\MongoCursorException $e) {
+ E($e->getMessage());
+ }
+ }
+
+ /**
+ * 生成下一条记录ID 用于自增非MongoId主键
+ * @access public
+ * @param string $pk 主键名
+ * @return integer
+ */
+ public function getMongoNextId($pk,$options=array())
+ {
+ if (isset($options['table'])) {
+ $this->switchCollection($options['table']);
+ }
+ if ($this->config['debug']) {
+ $this->queryStr = $this->_dbName . '.' . $this->_collectionName . '.find({},{' . $pk . ':1}).sort({' . $pk . ':-1}).limit(1)';
+ }
+ try {
+ $this->debug(true);
+ $result = $this->_collection->find(array(), array($pk => 1))->sort(array($pk => -1))->limit(1);
+ $this->debug(false);
+ } catch (\MongoCursorException $e) {
+ E($e->getMessage());
+ }
+ $data = $result->getNext();
+ return isset($data[$pk]) ? $data[$pk] + 1 : 1;
+ }
+
+ /**
+ * 更新记录
+ * @access public
+ * @param mixed $data 数据
+ * @param array $options 表达式
+ * @return bool
+ */
+ public function update($data, $options)
+ {
+ if (isset($options['table'])) {
+ $this->switchCollection($options['table']);
+ }
+ $this->executeTimes++;
+ N('db_write', 1); // 兼容代码
+ $this->model = $options['model'];
+ $query = $this->parseWhere(isset($options['where']) ? $options['where'] : array());
+ $set = $this->parseSet($data);
+ if ($this->config['debug']) {
+ $this->queryStr = $this->_dbName . '.' . $this->_collectionName . '.update(';
+ $this->queryStr .= $query ? json_encode($query) : '{}';
+ $this->queryStr .= ',' . json_encode($set) . ')';
+ }
+ try {
+ $this->debug(true);
+ if (isset($options['limit']) && 1 == $options['limit']) {
+ $multiple = array("multiple" => false);
+ } else {
+ $multiple = array("multiple" => true);
+ }
+ $result = $this->_collection->update($query, $set, $multiple);
+ $this->debug(false);
+ return $result;
+ } catch (\MongoCursorException $e) {
+ E($e->getMessage());
+ }
+ }
+
+ /**
+ * 删除记录
+ * @access public
+ * @param array $options 表达式
+ * @return false | integer
+ */
+ public function delete($options = array())
+ {
+ if (isset($options['table'])) {
+ $this->switchCollection($options['table']);
+ }
+ $query = $this->parseWhere(isset($options['where']) ? $options['where'] : array());
+ $this->model = $options['model'];
+ $this->executeTimes++;
+ N('db_write', 1); // 兼容代码
+ if ($this->config['debug']) {
+ $this->queryStr = $this->_dbName . '.' . $this->_collectionName . '.remove(' . json_encode($query) . ')';
+ }
+ try {
+ $this->debug(true);
+ $result = $this->_collection->remove($query);
+ $this->debug(false);
+ return $result;
+ } catch (\MongoCursorException $e) {
+ E($e->getMessage());
+ }
+ }
+
+ /**
+ * 清空记录
+ * @access public
+ * @param array $options 表达式
+ * @return false | integer
+ */
+ public function clear($options = array())
+ {
+ if (isset($options['table'])) {
+ $this->switchCollection($options['table']);
+ }
+ $this->model = $options['model'];
+ $this->executeTimes++;
+ N('db_write', 1); // 兼容代码
+ if ($this->config['debug']) {
+ $this->queryStr = $this->_dbName . '.' . $this->_collectionName . '.remove({})';
+ }
+ try {
+ $this->debug(true);
+ $result = $this->_collection->drop();
+ $this->debug(false);
+ return $result;
+ } catch (\MongoCursorException $e) {
+ E($e->getMessage());
+ }
+ }
+
+ /**
+ * 查找记录
+ * @access public
+ * @param array $options 表达式
+ * @return iterator
+ */
+ public function select($options = array())
+ {
+ if (isset($options['table'])) {
+ $this->switchCollection($options['table'], '', false);
+ }
+ $this->model = $options['model'];
+ $this->queryTimes++;
+ N('db_query', 1); // 兼容代码
+ $query = $this->parseWhere(isset($options['where']) ? $options['where'] : array());
+ $field = $this->parseField(isset($options['field']) ? $options['field'] : array());
+ try {
+ if ($this->config['debug']) {
+ $this->queryStr = $this->_dbName . '.' . $this->_collectionName . '.find(';
+ $this->queryStr .= $query ? json_encode($query) : '{}';
+ if (is_array($field) && count($field)) {
+ foreach ($field as $f => $v) {
+ $_field_array[$f] = $v ? 1 : 0;
+ }
+
+ $this->queryStr .= $field ? ', ' . json_encode($_field_array) : ', {}';
+ }
+ $this->queryStr .= ')';
+ }
+ $this->debug(true);
+ $_cursor = $this->_collection->find($query, $field);
+ if (!empty($options['order'])) {
+ $order = $this->parseOrder($options['order']);
+ if ($this->config['debug']) {
+ $this->queryStr .= '.sort(' . json_encode($order) . ')';
+ }
+ $_cursor = $_cursor->sort($order);
+ }
+ if (isset($options['page'])) {
+ // 根据页数计算limit
+ list($page, $length) = $options['page'];
+ $page = $page > 0 ? $page : 1;
+ $length = $length > 0 ? $length : (is_numeric($options['limit']) ? $options['limit'] : 20);
+ $offset = $length * ((int) $page - 1);
+ $options['limit'] = $offset . ',' . $length;
+ }
+ if (isset($options['limit'])) {
+ list($offset, $length) = $this->parseLimit($options['limit']);
+ if (!empty($offset)) {
+ if ($this->config['debug']) {
+ $this->queryStr .= '.skip(' . intval($offset) . ')';
+ }
+ $_cursor = $_cursor->skip(intval($offset));
+ }
+ if ($this->config['debug']) {
+ $this->queryStr .= '.limit(' . intval($length) . ')';
+ }
+ $_cursor = $_cursor->limit(intval($length));
+ }
+ $this->debug(false);
+ $this->_cursor = $_cursor;
+ $resultSet = iterator_to_array($_cursor);
+ return $resultSet;
+ } catch (\MongoCursorException $e) {
+ E($e->getMessage());
+ }
+ }
+
+ /**
+ * 查找某个记录
+ * @access public
+ * @param array $options 表达式
+ * @return array
+ */
+ public function find($options = array())
+ {
+ $options['limit'] = 1;
+ $find = $this->select($options);
+ return array_shift($find);
+ }
+
+ /**
+ * 统计记录数
+ * @access public
+ * @param array $options 表达式
+ * @return iterator
+ */
+ public function count($options = array())
+ {
+ if (isset($options['table'])) {
+ $this->switchCollection($options['table'], '', false);
+ }
+ $this->model = $options['model'];
+ $this->queryTimes++;
+ N('db_query', 1); // 兼容代码
+ $query = $this->parseWhere(isset($options['where']) ? $options['where'] : array());
+ if ($this->config['debug']) {
+ $this->queryStr = $this->_dbName . '.' . $this->_collectionName;
+ $this->queryStr .= $query ? '.find(' . json_encode($query) . ')' : '';
+ $this->queryStr .= '.count()';
+ }
+ try {
+ $this->debug(true);
+ $count = $this->_collection->count($query);
+ $this->debug(false);
+ return $count;
+ } catch (\MongoCursorException $e) {
+ E($e->getMessage());
+ }
+ }
+
+ public function group($keys, $initial, $reduce, $options = array())
+ {
+ if (isset($options['table']) && $this->_collectionName != $options['table']) {
+ $this->switchCollection($options['table'], '', false);
+ }
+
+ $cache = isset($options['cache']) ? $options['cache'] : false;
+ if ($cache) {
+ $key = is_string($cache['key']) ? $cache['key'] : md5(serialize($options));
+ $value = S($key, '', '', $cache['type']);
+ if (false !== $value) {
+ return $value;
+ }
+ }
+
+ $this->model = $options['model'];
+ $this->queryTimes++;
+ N('db_query', 1); // 兼容代码
+ $query = $this->parseWhere(isset($options['where']) ? $options['where'] : array());
+
+ if ($this->config['debug']) {
+ $this->queryStr = $this->_dbName . '.' . $this->_collectionName . '.group({key:' . json_encode($keys) . ',cond:' .
+ json_encode($options['condition']) . ',reduce:' .
+ json_encode($reduce) . ',initial:' .
+ json_encode($initial) . '})';
+ }
+ try {
+ $this->debug(true);
+ $option = array('condition' => $options['condition'], 'finalize' => $options['finalize'], 'maxTimeMS' => $options['maxTimeMS']);
+ $group = $this->_collection->group($keys, $initial, $reduce, $options);
+ $this->debug(false);
+
+ if ($cache && $group['ok']) {
+ S($key, $group, $cache['expire'], $cache['type']);
+ }
+
+ return $group;
+ } catch (\MongoCursorException $e) {
+ E($e->getMessage());
+ }
+ }
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ * @return array
+ */
+ public function getFields($collection = '')
+ {
+ if (!empty($collection) && $collection != $this->_collectionName) {
+ $this->switchCollection($collection, '', false);
+ }
+ $this->queryTimes++;
+ N('db_query', 1); // 兼容代码
+ if ($this->config['debug']) {
+ $this->queryStr = $this->_dbName . '.' . $this->_collectionName . '.findOne()';
+ }
+ try {
+ $this->debug(true);
+ $result = $this->_collection->findOne();
+ $this->debug(false);
+ } catch (\MongoCursorException $e) {
+ E($e->getMessage());
+ }
+ if ($result) {
+ // 存在数据则分析字段
+ $info = array();
+ foreach ($result as $key => $val) {
+ $info[$key] = array(
+ 'name' => $key,
+ 'type' => getType($val),
+ );
+ }
+ return $info;
+ }
+ // 暂时没有数据 返回false
+ return false;
+ }
+
+ /**
+ * 取得当前数据库的collection信息
+ * @access public
+ */
+ public function getTables()
+ {
+ if ($this->config['debug']) {
+ $this->queryStr = $this->_dbName . '.getCollenctionNames()';
+ }
+ $this->queryTimes++;
+ N('db_query', 1); // 兼容代码
+ $this->debug(true);
+ $list = $this->_mongo->listCollections();
+ $this->debug(false);
+ $info = array();
+ foreach ($list as $collection) {
+ $info[] = $collection->getName();
+ }
+ return $info;
+ }
+
+ /**
+ * 取得当前数据库的对象
+ * @access public
+ * @return object mongoClient
+ */
+ public function getDB()
+ {
+ return $this->_mongo;
+ }
+
+ /**
+ * 取得当前集合的对象
+ * @access public
+ * @return object MongoCollection
+ */
+ public function getCollection()
+ {
+ return $this->_collection;
+ }
+
+ /**
+ * set分析
+ * @access protected
+ * @param array $data
+ * @return string
+ */
+ protected function parseSet($data)
+ {
+ $result = array();
+ foreach ($data as $key => $val) {
+ if (is_array($val)) {
+ switch ($val[0]) {
+ case 'inc':
+ $result['$inc'][$key] = (float) $val[1];
+ break;
+ case 'set':
+ case 'unset':
+ case 'push':
+ case 'pushall':
+ case 'addtoset':
+ case 'pop':
+ case 'pull':
+ case 'pullall':
+ $result['$' . $val[0]][$key] = $val[1];
+ break;
+ default:
+ $result['$set'][$key] = $val;
+ }
+ } else {
+ $result['$set'][$key] = $val;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * order分析
+ * @access protected
+ * @param mixed $order
+ * @return array
+ */
+ protected function parseOrder($order)
+ {
+ if (is_string($order)) {
+ $array = explode(',', $order);
+ $order = array();
+ foreach ($array as $key => $val) {
+ $arr = explode(' ', trim($val));
+ if (isset($arr[1])) {
+ $arr[1] = 'asc' == $arr[1] ? 1 : -1;
+ } else {
+ $arr[1] = 1;
+ }
+ $order[$arr[0]] = $arr[1];
+ }
+ }
+ return $order;
+ }
+
+ /**
+ * limit分析
+ * @access protected
+ * @param mixed $limit
+ * @return array
+ */
+ protected function parseLimit($limit)
+ {
+ if (strpos($limit, ',')) {
+ $array = explode(',', $limit);
+ } else {
+ $array = array(0, $limit);
+ }
+ return $array;
+ }
+
+ /**
+ * field分析
+ * @access protected
+ * @param mixed $fields
+ * @return array
+ */
+ public function parseField($fields)
+ {
+ if (empty($fields)) {
+ $fields = array();
+ }
+ if (is_string($fields)) {
+ $_fields = explode(',', $fields);
+ $fields = array();
+ foreach ($_fields as $f) {
+ $fields[$f] = true;
+ }
+
+ } elseif (is_array($fields)) {
+ $_fields = $fields;
+ $fields = array();
+ foreach ($_fields as $f => $v) {
+ if (is_numeric($f)) {
+ $fields[$v] = true;
+ } else {
+ $fields[$f] = $v ? true : false;
+ }
+
+ }
+ }
+ return $fields;
+ }
+
+ /**
+ * where分析
+ * @access protected
+ * @param mixed $where
+ * @return array
+ */
+ public function parseWhere($where)
+ {
+ $query = array();
+ $return = array();
+ $_logic = '$and';
+ if (isset($where['_logic'])) {
+ $where['_logic'] = strtolower($where['_logic']);
+ $_logic = in_array($where['_logic'], array('or', 'xor', 'nor', 'and')) ? '$' . $where['_logic'] : $_logic;
+ unset($where['_logic']);
+ }
+ foreach ($where as $key => $val) {
+ if ('_id' != $key && 0 === strpos($key, '_')) {
+ // 解析特殊条件表达式
+ $parse = $this->parseThinkWhere($key, $val);
+ $query = array_merge($query, $parse);
+ } else {
+ // 查询字段的安全过滤
+ if (!preg_match('/^[A-Z_\|\&\-.a-z0-9]+$/', trim($key))) {
+ E(L('_ERROR_QUERY_') . ':' . $key);
+ }
+ $key = trim($key);
+ if (strpos($key, '|')) {
+ $array = explode('|', $key);
+ $str = array();
+ foreach ($array as $k) {
+ $str[] = $this->parseWhereItem($k, $val);
+ }
+ $query['$or'] = $str;
+ } elseif (strpos($key, '&')) {
+ $array = explode('&', $key);
+ $str = array();
+ foreach ($array as $k) {
+ $str[] = $this->parseWhereItem($k, $val);
+ }
+ $query = array_merge($query, $str);
+ } else {
+ $str = $this->parseWhereItem($key, $val);
+ $query = array_merge($query, $str);
+ }
+ }
+ }
+ if ('$and' == $_logic) {
+ return $query;
+ }
+
+ foreach ($query as $key => $val) {
+ $return[$_logic][] = array($key => $val);
+ }
+
+ return $return;
+ }
+
+ /**
+ * 特殊条件分析
+ * @access protected
+ * @param string $key
+ * @param mixed $val
+ * @return string
+ */
+ protected function parseThinkWhere($key, $val)
+ {
+ $query = array();
+ $_logic = array('or', 'xor', 'nor', 'and');
+
+ switch ($key) {
+ case '_query': // 字符串模式查询条件
+ parse_str($val, $query);
+ if (isset($query['_logic']) && strtolower($query['_logic']) == 'or') {
+ unset($query['_logic']);
+ $query['$or'] = $query;
+ }
+ break;
+ case '_complex': // 子查询模式查询条件
+ $__logic = strtolower($val['_logic']);
+ if (isset($val['_logic']) && in_array($__logic, $_logic)) {
+ unset($val['_logic']);
+ $query['$' . $__logic] = $val;
+ }
+ break;
+ case '_string': // MongoCode查询
+ $query['$where'] = new \MongoCode($val);
+ break;
+ }
+ //兼容 MongoClient OR条件查询方法
+ if (isset($query['$or']) && !is_array(current($query['$or']))) {
+ $val = array();
+ foreach ($query['$or'] as $k => $v) {
+ $val[] = array($k => $v);
+ }
+
+ $query['$or'] = $val;
+ }
+ return $query;
+ }
+
+ /**
+ * where子单元分析
+ * @access protected
+ * @param string $key
+ * @param mixed $val
+ * @return array
+ */
+ protected function parseWhereItem($key, $val)
+ {
+ $query = array();
+ if (is_array($val)) {
+ if (is_string($val[0])) {
+ $con = strtolower($val[0]);
+ if (in_array($con, array('neq', 'ne', 'gt', 'egt', 'gte', 'lt', 'lte', 'elt'))) {
+ // 比较运算
+ $k = '$' . $this->comparison[$con];
+ $query[$key] = array($k => $val[1]);
+ } elseif ('like' == $con) {
+ // 模糊查询 采用正则方式
+ $query[$key] = new \MongoRegex("/" . $val[1] . "/");
+ } elseif ('mod' == $con) {
+ // mod 查询
+ $query[$key] = array('$mod' => $val[1]);
+ } elseif ('regex' == $con) {
+ // 正则查询
+ $query[$key] = new \MongoRegex($val[1]);
+ } elseif (in_array($con, array('in', 'nin', 'not in'))) {
+ // IN NIN 运算
+ $data = is_string($val[1]) ? explode(',', $val[1]) : $val[1];
+ $k = '$' . $this->comparison[$con];
+ $query[$key] = array($k => $data);
+ } elseif ('all' == $con) {
+ // 满足所有指定条件
+ $data = is_string($val[1]) ? explode(',', $val[1]) : $val[1];
+ $query[$key] = array('$all' => $data);
+ } elseif ('between' == $con) {
+ // BETWEEN运算
+ $data = is_string($val[1]) ? explode(',', $val[1]) : $val[1];
+ $query[$key] = array('$gte' => $data[0], '$lte' => $data[1]);
+ } elseif ('not between' == $con) {
+ $data = is_string($val[1]) ? explode(',', $val[1]) : $val[1];
+ $query[$key] = array('$lt' => $data[0], '$gt' => $data[1]);
+ } elseif ('exp' == $con) {
+ // 表达式查询
+ $query['$where'] = new \MongoCode($val[1]);
+ } elseif ('exists' == $con) {
+ // 字段是否存在
+ $query[$key] = array('$exists' => (bool) $val[1]);
+ } elseif ('size' == $con) {
+ // 限制属性大小
+ $query[$key] = array('$size' => intval($val[1]));
+ } elseif ('type' == $con) {
+ // 限制字段类型 1 浮点型 2 字符型 3 对象或者MongoDBRef 5 MongoBinData 7 MongoId 8 布尔型 9 MongoDate 10 NULL 15 MongoCode 16 32位整型 17 MongoTimestamp 18 MongoInt64 如果是数组的话判断元素的类型
+ $query[$key] = array('$type' => intval($val[1]));
+ } else {
+ $query[$key] = $val;
+ }
+ return $query;
+ }
+ }
+ $query[$key] = $val;
+ return $query;
+ }
+}
diff --git a/Framework/Library/Think/Db/Driver/Mysql.class.php b/Framework/Library/Think/Db/Driver/Mysql.class.php
new file mode 100644
index 00000000..4b847ea6
--- /dev/null
+++ b/Framework/Library/Think/Db/Driver/Mysql.class.php
@@ -0,0 +1,270 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think\Db\Driver;
+
+use Think\Db\Driver;
+
+/**
+ * mysql数据库驱动
+ */
+class Mysql extends Driver
+{
+
+ /**
+ * 解析pdo连接的dsn信息
+ * @access public
+ * @param array $config 连接信息
+ * @return string
+ */
+ protected function parseDsn($config)
+ {
+ $dsn = 'mysql:dbname=' . $config['database'] . ';host=' . $config['hostname'];
+ if (!empty($config['hostport'])) {
+ $dsn .= ';port=' . $config['hostport'];
+ } elseif (!empty($config['socket'])) {
+ $dsn .= ';unix_socket=' . $config['socket'];
+ }
+
+ if (!empty($config['charset'])) {
+ //为兼容各版本PHP,用两种方式设置编码
+ $this->options[\PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES ' . $config['charset'];
+ $dsn .= ';charset=' . $config['charset'];
+ }
+ return $dsn;
+ }
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ */
+ public function getFields($tableName)
+ {
+ $this->initConnect(true);
+ list($tableName) = explode(' ', $tableName);
+ if (strpos($tableName, '.')) {
+ list($dbName, $tableName) = explode('.', $tableName);
+ $sql = 'SHOW COLUMNS FROM `' . $dbName . '`.`' . $tableName . '`';
+ } else {
+ $sql = 'SHOW COLUMNS FROM `' . $tableName . '`';
+ }
+
+ $result = $this->query($sql);
+ $info = array();
+ if ($result) {
+ foreach ($result as $key => $val) {
+ if (\PDO::CASE_LOWER != $this->_linkID->getAttribute(\PDO::ATTR_CASE)) {
+ $val = array_change_key_case($val, CASE_LOWER);
+ }
+ $info[$val['field']] = array(
+ 'name' => $val['field'],
+ 'type' => $val['type'],
+ 'notnull' => (bool) ('' === $val['null']), // not null is empty, null is yes
+ 'default' => $val['default'],
+ 'primary' => (strtolower($val['key']) == 'pri'),
+ 'autoinc' => (strtolower($val['extra']) == 'auto_increment'),
+ );
+ }
+ }
+ return $info;
+ }
+
+ /**
+ * 取得数据库的表信息
+ * @access public
+ */
+ public function getTables($dbName = '')
+ {
+ $sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES ';
+ $result = $this->query($sql);
+ $info = array();
+ foreach ($result as $key => $val) {
+ $info[$key] = current($val);
+ }
+ return $info;
+ }
+
+ /**
+ * 字段和表名处理
+ * @access protected
+ * @param string $key
+ * @return string
+ */
+ protected function parseKey($key)
+ {
+ $key = trim($key);
+ if (!is_numeric($key) && !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) {
+ $key = '`' . $key . '`';
+ }
+ return $key;
+ }
+
+ /**
+ * 随机排序
+ * @access protected
+ * @return string
+ */
+ protected function parseRand()
+ {
+ return 'rand()';
+ }
+
+ /**
+ * 批量插入记录
+ * @access public
+ * @param mixed $dataSet 数据集
+ * @param array $options 参数表达式
+ * @param boolean $replace 是否replace
+ * @return false | integer
+ */
+ public function insertAll($dataSet, $options = array(), $replace = false)
+ {
+ $values = array();
+ $this->model = $options['model'];
+ if (!is_array($dataSet[0])) {
+ return false;
+ }
+
+ $this->parseBind(!empty($options['bind']) ? $options['bind'] : array());
+ $fields = array_map(array($this, 'parseKey'), array_keys($dataSet[0]));
+ foreach ($dataSet as $data) {
+ $value = array();
+ foreach ($data as $key => $val) {
+ if (is_array($val) && 'exp' == $val[0]) {
+ $value[] = $val[1];
+ } elseif (is_null($val)) {
+ $value[] = 'NULL';
+ } elseif (is_scalar($val)) {
+ if (0 === strpos($val, ':') && in_array($val, array_keys($this->bind))) {
+ $value[] = $this->parseValue($val);
+ } else {
+ $name = count($this->bind);
+ $value[] = ':' . $name;
+ $this->bindParam($name, $val);
+ }
+ }
+ }
+ $values[] = '(' . implode(',', $value) . ')';
+ }
+ // 兼容数字传入方式
+ $replace = (is_numeric($replace) && $replace > 0) ? true : $replace;
+ $sql = (true === $replace ? 'REPLACE' : 'INSERT') . ' INTO ' . $this->parseTable($options['table']) . ' (' . implode(',', $fields) . ') VALUES ' . implode(',', $values) . $this->parseDuplicate($replace);
+ $sql .= $this->parseComment(!empty($options['comment']) ? $options['comment'] : '');
+ return $this->execute($sql, !empty($options['fetch_sql']) ? true : false);
+ }
+
+ /**
+ * ON DUPLICATE KEY UPDATE 分析
+ * @access protected
+ * @param mixed $duplicate
+ * @return string
+ */
+ protected function parseDuplicate($duplicate)
+ {
+ // 布尔值或空则返回空字符串
+ if (is_bool($duplicate) || empty($duplicate)) {
+ return '';
+ }
+
+ if (is_string($duplicate)) {
+ // field1,field2 转数组
+ $duplicate = explode(',', $duplicate);
+ } elseif (is_object($duplicate)) {
+ // 对象转数组
+ $duplicate = get_class_vars($duplicate);
+ }
+ $updates = array();
+ foreach ((array) $duplicate as $key => $val) {
+ if (is_numeric($key)) {
+ // array('field1', 'field2', 'field3') 解析为 ON DUPLICATE KEY UPDATE field1=VALUES(field1), field2=VALUES(field2), field3=VALUES(field3)
+ $updates[] = $this->parseKey($val) . "=VALUES(" . $this->parseKey($val) . ")";
+ } else {
+ if (is_scalar($val)) // 兼容标量传值方式
+ {
+ $val = array('value', $val);
+ }
+
+ if (!isset($val[1]) && !is_null($val[1])) {
+ continue;
+ }
+
+ switch ($val[0]) {
+ case 'exp': // 表达式
+ $updates[] = $this->parseKey($key) . "=($val[1])";
+ break;
+ case 'value':// 值
+ default:
+ $name = count($this->bind);
+ $updates[] = $this->parseKey($key) . "=:" . $name;
+ $this->bindParam($name, $val[1]);
+ break;
+ }
+ }
+ }
+ if (empty($updates)) {
+ return '';
+ }
+
+ return " ON DUPLICATE KEY UPDATE " . join(', ', $updates);
+ }
+
+ /**
+ * 执行存储过程查询 返回多个数据集
+ * @access public
+ * @param string $str sql指令
+ * @param boolean $fetchSql 不执行只是获取SQL
+ * @return mixed
+ */
+ public function procedure($str, $fetchSql = false)
+ {
+ $this->initConnect(false);
+ $this->_linkID->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_WARNING);
+ if (!$this->_linkID) {
+ return false;
+ }
+
+ $this->queryStr = $str;
+ if ($fetchSql) {
+ return $this->queryStr;
+ }
+ //释放前次的查询结果
+ if (!empty($this->PDOStatement)) {
+ $this->free();
+ }
+
+ $this->queryTimes++;
+ N('db_query', 1); // 兼容代码
+ // 调试开始
+ $this->debug(true);
+ $this->PDOStatement = $this->_linkID->prepare($str);
+ if (false === $this->PDOStatement) {
+ $this->error();
+ return false;
+ }
+ try {
+ $result = $this->PDOStatement->execute();
+ // 调试结束
+ $this->debug(false);
+ do {
+ $result = $this->PDOStatement->fetchAll(\PDO::FETCH_ASSOC);
+ if ($result) {
+ $resultArr[] = $result;
+ }
+ } while ($this->PDOStatement->nextRowset());
+ $this->_linkID->setAttribute(\PDO::ATTR_ERRMODE, $this->options[\PDO::ATTR_ERRMODE]);
+ return $resultArr;
+ } catch (\PDOException $e) {
+ $this->error();
+ $this->_linkID->setAttribute(\PDO::ATTR_ERRMODE, $this->options[\PDO::ATTR_ERRMODE]);
+ return false;
+ }
+ }
+}
diff --git a/Framework/Library/Think/Db/Driver/Oracle.class.php b/Framework/Library/Think/Db/Driver/Oracle.class.php
new file mode 100644
index 00000000..84152e3f
--- /dev/null
+++ b/Framework/Library/Think/Db/Driver/Oracle.class.php
@@ -0,0 +1,198 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think\Db\Driver;
+
+use Think\Db\Driver;
+
+/**
+ * Oracle数据库驱动
+ */
+class Oracle extends Driver
+{
+
+ private $table = '';
+ protected $selectSql = 'SELECT * FROM (SELECT thinkphp.*, rownum AS numrow FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%) thinkphp ) %LIMIT%%COMMENT%';
+
+ /**
+ * 解析pdo连接的dsn信息
+ * @access public
+ * @param array $config 连接信息
+ * @return string
+ */
+ protected function parseDsn($config)
+ {
+ $dsn = 'oci:dbname=//' . $config['hostname'] . ($config['hostport'] ? ':' . $config['hostport'] : '') . '/' . $config['database'];
+ if (!empty($config['charset'])) {
+ $dsn .= ';charset=' . $config['charset'];
+ }
+ return $dsn;
+ }
+
+ /**
+ * 执行语句
+ * @access public
+ * @param string $str sql指令
+ * @param boolean $fetchSql 不执行只是获取SQL
+ * @return integer
+ */
+ public function execute($str, $fetchSql = false)
+ {
+ $this->initConnect(true);
+ if (!$this->_linkID) {
+ return false;
+ }
+
+ $this->queryStr = $str;
+ if (!empty($this->bind)) {
+ $that = $this;
+ $this->queryStr = strtr($this->queryStr, array_map(function ($val) use ($that) {return '\'' . $that->escapeString($val) . '\'';}, $this->bind));
+ }
+ if ($fetchSql) {
+ return $this->queryStr;
+ }
+ $flag = false;
+ if (preg_match("/^\s*(INSERT\s+INTO)\s+(\w+)\s+/i", $str, $match)) {
+ $this->table = C("DB_SEQUENCE_PREFIX") . str_ireplace(C("DB_PREFIX"), "", $match[2]);
+ $flag = (boolean) $this->query("SELECT * FROM user_sequences WHERE sequence_name='" . strtoupper($this->table) . "'");
+ }
+ //释放前次的查询结果
+ if (!empty($this->PDOStatement)) {
+ $this->free();
+ }
+
+ $this->executeTimes++;
+ N('db_write', 1); // 兼容代码
+ // 记录开始执行时间
+ $this->debug(true);
+ $this->PDOStatement = $this->_linkID->prepare($str);
+ if (false === $this->PDOStatement) {
+ $this->error();
+ return false;
+ }
+ foreach ($this->bind as $key => $val) {
+ if (is_array($val)) {
+ $this->PDOStatement->bindValue($key, $val[0], $val[1]);
+ } else {
+ $this->PDOStatement->bindValue($key, $val);
+ }
+ }
+ $this->bind = array();
+ $result = $this->PDOStatement->execute();
+ $this->debug(false);
+ if (false === $result) {
+ $this->error();
+ return false;
+ } else {
+ $this->numRows = $this->PDOStatement->rowCount();
+ if ($flag || preg_match("/^\s*(INSERT\s+INTO|REPLACE\s+INTO)\s+/i", $str)) {
+ $this->lastInsID = $this->_linkID->lastInsertId();
+ }
+ return $this->numRows;
+ }
+ }
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ */
+ public function getFields($tableName)
+ {
+ list($tableName) = explode(' ', $tableName);
+ $result = $this->query("select a.column_name,data_type,decode(nullable,'Y',0,1) notnull,data_default,decode(a.column_name,b.column_name,1,0) pk "
+ . "from user_tab_columns a,(select column_name from user_constraints c,user_cons_columns col "
+ . "where c.constraint_name=col.constraint_name and c.constraint_type='P'and c.table_name='" . strtoupper($tableName)
+ . "') b where table_name='" . strtoupper($tableName) . "' and a.column_name=b.column_name(+)");
+ $info = array();
+ if ($result) {
+ foreach ($result as $key => $val) {
+ $info[strtolower($val['column_name'])] = array(
+ 'name' => strtolower($val['column_name']),
+ 'type' => strtolower($val['data_type']),
+ 'notnull' => $val['notnull'],
+ 'default' => $val['data_default'],
+ 'primary' => $val['pk'],
+ 'autoinc' => $val['pk'],
+ );
+ }
+ }
+ return $info;
+ }
+
+ /**
+ * 取得数据库的表信息(暂时实现取得用户表信息)
+ * @access public
+ */
+ public function getTables($dbName = '')
+ {
+ $result = $this->query("select table_name from user_tables");
+ $info = array();
+ foreach ($result as $key => $val) {
+ $info[$key] = current($val);
+ }
+ return $info;
+ }
+
+ /**
+ * SQL指令安全过滤
+ * @access public
+ * @param string $str SQL指令
+ * @return string
+ */
+ public function escapeString($str)
+ {
+ return str_ireplace("'", "''", $str);
+ }
+
+ /**
+ * limit
+ * @access public
+ * @return string
+ */
+ public function parseLimit($limit)
+ {
+ $limitStr = '';
+ if (!empty($limit)) {
+ $limit = explode(',', $limit);
+ if (count($limit) > 1) {
+ $limitStr = "(numrow>" . $limit[0] . ") AND (numrow<=" . ($limit[0] + $limit[1]) . ")";
+ } else {
+ $limitStr = "(numrow>0 AND numrow<=" . $limit[0] . ")";
+ }
+
+ }
+ return $limitStr ? ' WHERE ' . $limitStr : '';
+ }
+
+ /**
+ * 设置锁机制
+ * @access protected
+ * @return string
+ */
+ protected function parseLock($lock = false)
+ {
+ if (!$lock) {
+ return '';
+ }
+
+ return ' FOR UPDATE NOWAIT ';
+ }
+
+ /**
+ * 随机排序
+ * @access protected
+ * @return string
+ */
+ protected function parseRand()
+ {
+ return 'DBMS_RANDOM.value';
+ }
+}
diff --git a/Framework/Library/Think/Db/Driver/Pgsql.class.php b/Framework/Library/Think/Db/Driver/Pgsql.class.php
new file mode 100644
index 00000000..f887c742
--- /dev/null
+++ b/Framework/Library/Think/Db/Driver/Pgsql.class.php
@@ -0,0 +1,106 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think\Db\Driver;
+
+use Think\Db\Driver;
+
+/**
+ * Pgsql数据库驱动
+ */
+class Pgsql extends Driver
+{
+
+ /**
+ * 解析pdo连接的dsn信息
+ * @access public
+ * @param array $config 连接信息
+ * @return string
+ */
+ protected function parseDsn($config)
+ {
+ $dsn = 'pgsql:dbname=' . $config['database'] . ';host=' . $config['hostname'];
+ if (!empty($config['hostport'])) {
+ $dsn .= ';port=' . $config['hostport'];
+ }
+ return $dsn;
+ }
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ * @return array
+ */
+ public function getFields($tableName)
+ {
+ list($tableName) = explode(' ', $tableName);
+ $result = $this->query('select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');');
+ $info = array();
+ if ($result) {
+ foreach ($result as $key => $val) {
+ $info[$val['field']] = array(
+ 'name' => $val['field'],
+ 'type' => $val['type'],
+ 'notnull' => (bool) ('' === $val['null']), // not null is empty, null is yes
+ 'default' => $val['default'],
+ 'primary' => (strtolower($val['key']) == 'pri'),
+ 'autoinc' => (strtolower($val['extra']) == 'auto_increment'),
+ );
+ }
+ }
+ return $info;
+ }
+
+ /**
+ * 取得数据库的表信息
+ * @access public
+ * @return array
+ */
+ public function getTables($dbName = '')
+ {
+ $result = $this->query("select tablename as Tables_in_test from pg_tables where schemaname ='public'");
+ $info = array();
+ foreach ($result as $key => $val) {
+ $info[$key] = current($val);
+ }
+ return $info;
+ }
+
+ /**
+ * limit分析
+ * @access protected
+ * @param mixed $lmit
+ * @return string
+ */
+ public function parseLimit($limit)
+ {
+ $limitStr = '';
+ if (!empty($limit)) {
+ $limit = explode(',', $limit);
+ if (count($limit) > 1) {
+ $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' ';
+ } else {
+ $limitStr .= ' LIMIT ' . $limit[0] . ' ';
+ }
+ }
+ return $limitStr;
+ }
+
+ /**
+ * 随机排序
+ * @access protected
+ * @return string
+ */
+ protected function parseRand()
+ {
+ return 'RANDOM()';
+ }
+}
diff --git a/Framework/Library/Think/Db/Driver/Sqlite.class.php b/Framework/Library/Think/Db/Driver/Sqlite.class.php
new file mode 100644
index 00000000..24cb6ccd
--- /dev/null
+++ b/Framework/Library/Think/Db/Driver/Sqlite.class.php
@@ -0,0 +1,115 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think\Db\Driver;
+
+use Think\Db\Driver;
+
+/**
+ * Sqlite数据库驱动
+ */
+class Sqlite extends Driver
+{
+
+ /**
+ * 解析pdo连接的dsn信息
+ * @access public
+ * @param array $config 连接信息
+ * @return string
+ */
+ protected function parseDsn($config)
+ {
+ $dsn = 'sqlite:' . $config['database'];
+ return $dsn;
+ }
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ * @return array
+ */
+ public function getFields($tableName)
+ {
+ list($tableName) = explode(' ', $tableName);
+ $result = $this->query('PRAGMA table_info( ' . $tableName . ' )');
+ $info = array();
+ if ($result) {
+ foreach ($result as $key => $val) {
+ $info[$val['name']] = array(
+ 'name' => $val['name'],
+ 'type' => $val['type'],
+ 'notnull' => (bool) (1 === $val['notnull']),
+ 'default' => $val['dflt_value'],
+ 'primary' => '1' == $val['pk'],
+ 'autoinc' => false,
+ );
+ }
+ }
+ return $info;
+ }
+
+ /**
+ * 取得数据库的表信息
+ * @access public
+ * @return array
+ */
+ public function getTables($dbName = '')
+ {
+ $result = $this->query("SELECT name FROM sqlite_master WHERE type='table' "
+ . "UNION ALL SELECT name FROM sqlite_temp_master "
+ . "WHERE type='table' ORDER BY name");
+ $info = array();
+ foreach ($result as $key => $val) {
+ $info[$key] = current($val);
+ }
+ return $info;
+ }
+
+ /**
+ * SQL指令安全过滤
+ * @access public
+ * @param string $str SQL指令
+ * @return string
+ */
+ public function escapeString($str)
+ {
+ return str_ireplace("'", "''", $str);
+ }
+
+ /**
+ * limit
+ * @access public
+ * @return string
+ */
+ public function parseLimit($limit)
+ {
+ $limitStr = '';
+ if (!empty($limit)) {
+ $limit = explode(',', $limit);
+ if (count($limit) > 1) {
+ $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' ';
+ } else {
+ $limitStr .= ' LIMIT ' . $limit[0] . ' ';
+ }
+ }
+ return $limitStr;
+ }
+
+ /**
+ * 随机排序
+ * @access protected
+ * @return string
+ */
+ protected function parseRand()
+ {
+ return 'RANDOM()';
+ }
+}
diff --git a/Framework/Library/Think/Db/Driver/Sqlsrv.class.php b/Framework/Library/Think/Db/Driver/Sqlsrv.class.php
new file mode 100644
index 00000000..f7c7e407
--- /dev/null
+++ b/Framework/Library/Think/Db/Driver/Sqlsrv.class.php
@@ -0,0 +1,181 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think\Db\Driver;
+
+use PDO;
+use Think\Db\Driver;
+
+/**
+ * Sqlsrv数据库驱动
+ */
+class Sqlsrv extends Driver
+{
+ protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING% %UNION%) AS thinkphp) AS T1 %LIMIT%%COMMENT%';
+ // PDO连接参数
+ protected $options = array(
+ PDO::ATTR_CASE => PDO::CASE_LOWER,
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+ PDO::ATTR_STRINGIFY_FETCHES => false,
+ PDO::SQLSRV_ATTR_ENCODING => PDO::SQLSRV_ENCODING_UTF8,
+ );
+
+ /**
+ * 解析pdo连接的dsn信息
+ * @access public
+ * @param array $config 连接信息
+ * @return string
+ */
+ protected function parseDsn($config)
+ {
+ $dsn = 'sqlsrv:Database=' . $config['database'] . ';Server=' . $config['hostname'];
+ if (!empty($config['hostport'])) {
+ $dsn .= ',' . $config['hostport'];
+ }
+ return $dsn;
+ }
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ * @return array
+ */
+ public function getFields($tableName)
+ {
+ list($tableName) = explode(' ', $tableName);
+ $result = $this->query("SELECT column_name, data_type, column_default, is_nullable
+ FROM information_schema.tables AS t
+ JOIN information_schema.columns AS c
+ ON t.table_catalog = c.table_catalog
+ AND t.table_schema = c.table_schema
+ AND t.table_name = c.table_name
+ WHERE t.table_name = '$tableName'");
+ $info = array();
+ if ($result) {
+ foreach ($result as $key => $val) {
+ $info[$val['column_name']] = array(
+ 'name' => $val['column_name'],
+ 'type' => $val['data_type'],
+ 'notnull' => (bool) ('' === $val['is_nullable']), // not null is empty, null is yes
+ 'default' => $val['column_default'],
+ 'primary' => false,
+ 'autoinc' => false,
+ );
+ }
+ }
+ return $info;
+ }
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ * @return array
+ */
+ public function getTables($dbName = '')
+ {
+ $result = $this->query("SELECT TABLE_NAME
+ FROM INFORMATION_SCHEMA.TABLES
+ WHERE TABLE_TYPE = 'BASE TABLE'
+ ");
+ $info = array();
+ foreach ($result as $key => $val) {
+ $info[$key] = current($val);
+ }
+ return $info;
+ }
+
+ /**
+ * order分析
+ * @access protected
+ * @param mixed $order
+ * @return string
+ */
+ protected function parseOrder($order)
+ {
+ return !empty($order) ? ' ORDER BY ' . $order : ' ORDER BY rand()';
+ }
+
+ /**
+ * 字段名分析
+ * @access protected
+ * @param string $key
+ * @return string
+ */
+ protected function parseKey($key)
+ {
+ $key = trim($key);
+ if (!is_numeric($key) && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) {
+ $key = '[' . $key . ']';
+ }
+ return $key;
+ }
+
+ /**
+ * limit
+ * @access public
+ * @param mixed $limit
+ * @return string
+ */
+ public function parseLimit($limit)
+ {
+ if (empty($limit)) {
+ return '';
+ }
+
+ $limit = explode(',', $limit);
+ if (count($limit) > 1) {
+ $limitStr = '(T1.ROW_NUMBER BETWEEN ' . $limit[0] . ' + 1 AND ' . $limit[0] . ' + ' . $limit[1] . ')';
+ } else {
+ $limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND ' . $limit[0] . ")";
+ }
+
+ return 'WHERE ' . $limitStr;
+ }
+
+ /**
+ * 更新记录
+ * @access public
+ * @param mixed $data 数据
+ * @param array $options 表达式
+ * @return false | integer
+ */
+ public function update($data, $options)
+ {
+ $this->model = $options['model'];
+ $this->parseBind(!empty($options['bind']) ? $options['bind'] : array());
+ $sql = 'UPDATE '
+ . $this->parseTable($options['table'])
+ . $this->parseSet($data)
+ . $this->parseWhere(!empty($options['where']) ? $options['where'] : '')
+ . $this->parseLock(isset($options['lock']) ? $options['lock'] : false)
+ . $this->parseComment(!empty($options['comment']) ? $options['comment'] : '');
+ return $this->execute($sql, !empty($options['fetch_sql']) ? true : false);
+ }
+
+ /**
+ * 删除记录
+ * @access public
+ * @param array $options 表达式
+ * @return false | integer
+ */
+ public function delete($options = array())
+ {
+ $this->model = $options['model'];
+ $this->parseBind(!empty($options['bind']) ? $options['bind'] : array());
+ $sql = 'DELETE FROM '
+ . $this->parseTable($options['table'])
+ . $this->parseWhere(!empty($options['where']) ? $options['where'] : '')
+ . $this->parseLock(isset($options['lock']) ? $options['lock'] : false)
+ . $this->parseComment(!empty($options['comment']) ? $options['comment'] : '');
+ return $this->execute($sql, !empty($options['fetch_sql']) ? true : false);
+ }
+
+}
diff --git a/Framework/Library/Think/Db/Lite.class.php b/Framework/Library/Think/Db/Lite.class.php
new file mode 100644
index 00000000..8afc496c
--- /dev/null
+++ b/Framework/Library/Think/Db/Lite.class.php
@@ -0,0 +1,533 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think\Db;
+
+use PDO;
+use Think\Config;
+use Think\Debug;
+
+class Lite
+{
+ // PDO操作实例
+ protected $PDOStatement = null;
+ // 当前操作所属的模型名
+ protected $model = '_think_';
+ // 当前SQL指令
+ protected $queryStr = '';
+ protected $modelSql = array();
+ // 最后插入ID
+ protected $lastInsID = null;
+ // 返回或者影响记录数
+ protected $numRows = 0;
+ // 事物操作PDO实例
+ protected $transPDO = null;
+ // 事务指令数
+ protected $transTimes = 0;
+ // 错误信息
+ protected $error = '';
+ // 数据库连接ID 支持多个连接
+ protected $linkID = array();
+ // 当前连接ID
+ protected $_linkID = null;
+ // 数据库连接参数配置
+ protected $config = array(
+ 'type' => '', // 数据库类型
+ 'hostname' => '127.0.0.1', // 服务器地址
+ 'database' => '', // 数据库名
+ 'username' => '', // 用户名
+ 'password' => '', // 密码
+ 'hostport' => '', // 端口
+ 'dsn' => '', //
+ 'params' => array(), // 数据库连接参数
+ 'charset' => 'utf8', // 数据库编码默认采用utf8
+ 'prefix' => '', // 数据库表前缀
+ 'debug' => false, // 数据库调试模式
+ 'deploy' => 0, // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
+ 'rw_separate' => false, // 数据库读写是否分离 主从式有效
+ 'master_num' => 1, // 读写分离后 主服务器数量
+ 'slave_no' => '', // 指定从服务器序号
+ );
+ // 数据库表达式
+ protected $comparison = array('eq' => '=', 'neq' => '<>', 'gt' => '>', 'egt' => '>=', 'lt' => '<', 'elt' => '<=', 'notlike' => 'NOT LIKE', 'like' => 'LIKE', 'in' => 'IN', 'notin' => 'NOT IN');
+ // 查询表达式
+ protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%%LIMIT% %UNION%%COMMENT%';
+ // 查询次数
+ protected $queryTimes = 0;
+ // 执行次数
+ protected $executeTimes = 0;
+ // PDO连接参数
+ protected $options = array(
+ PDO::ATTR_CASE => PDO::CASE_LOWER,
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+ PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
+ PDO::ATTR_STRINGIFY_FETCHES => false,
+ );
+
+ /**
+ * 架构函数 读取数据库配置信息
+ * @access public
+ * @param array $config 数据库配置数组
+ */
+ public function __construct($config = '')
+ {
+ if (!empty($config)) {
+ $this->config = array_merge($this->config, $config);
+ if (is_array($this->config['params'])) {
+ $this->options += $this->config['params'];
+ }
+ }
+ }
+
+ /**
+ * 连接数据库方法
+ * @access public
+ */
+ public function connect($config = '', $linkNum = 0)
+ {
+ if (!isset($this->linkID[$linkNum])) {
+ if (empty($config)) {
+ $config = $this->config;
+ }
+
+ try {
+ if (empty($config['dsn'])) {
+ $config['dsn'] = $this->parseDsn($config);
+ }
+ if (version_compare(PHP_VERSION, '5.3.6', '<=')) {
+ //禁用模拟预处理语句
+ $this->options[PDO::ATTR_EMULATE_PREPARES] = false;
+ }
+ $this->linkID[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $this->options);
+ } catch (\PDOException $e) {
+ E($e->getMessage());
+ }
+ }
+ return $this->linkID[$linkNum];
+ }
+
+ /**
+ * 解析pdo连接的dsn信息
+ * @access public
+ * @param array $config 连接信息
+ * @return string
+ */
+ protected function parseDsn($config)
+ {}
+
+ /**
+ * 释放查询结果
+ * @access public
+ */
+ public function free()
+ {
+ $this->PDOStatement = null;
+ }
+
+ /**
+ * 执行查询 返回数据集
+ * @access public
+ * @param string $str sql指令
+ * @param array $bind 参数绑定
+ * @return mixed
+ */
+ public function query($str, $bind = array())
+ {
+ $this->initConnect(false);
+ if (!$this->_linkID) {
+ return false;
+ }
+
+ $this->queryStr = $str;
+ if (!empty($bind)) {
+ $that = $this;
+ $this->queryStr = strtr($this->queryStr, array_map(function ($val) use ($that) {return '\'' . $that->escapeString($val) . '\'';}, $bind));
+ }
+ //释放前次的查询结果
+ if (!empty($this->PDOStatement)) {
+ $this->free();
+ }
+
+ $this->queryTimes++;
+ N('db_query', 1); // 兼容代码
+ // 调试开始
+ $this->debug(true);
+ $this->PDOStatement = $this->_linkID->prepare($str);
+ if (false === $this->PDOStatement) {
+ E($this->error());
+ }
+
+ foreach ($bind as $key => $val) {
+ if (is_array($val)) {
+ $this->PDOStatement->bindValue($key, $val[0], $val[1]);
+ } else {
+ $this->PDOStatement->bindValue($key, $val);
+ }
+ }
+ $result = $this->PDOStatement->execute();
+ // 调试结束
+ $this->debug(false);
+ if (false === $result) {
+ $this->error();
+ return false;
+ } else {
+ return $this->getResult();
+ }
+ }
+
+ /**
+ * 执行语句
+ * @access public
+ * @param string $str sql指令
+ * @param array $bind 参数绑定
+ * @return integer
+ */
+ public function execute($str, $bind = array())
+ {
+ $this->initConnect(true);
+ if (!$this->_linkID) {
+ return false;
+ }
+
+ $this->queryStr = $str;
+ if (!empty($bind)) {
+ $that = $this;
+ $this->queryStr = strtr($this->queryStr, array_map(function ($val) use ($that) {return '\'' . $that->escapeString($val) . '\'';}, $bind));
+ }
+ //释放前次的查询结果
+ if (!empty($this->PDOStatement)) {
+ $this->free();
+ }
+
+ $this->executeTimes++;
+ N('db_write', 1); // 兼容代码
+ // 记录开始执行时间
+ $this->debug(true);
+ $this->PDOStatement = $this->_linkID->prepare($str);
+ if (false === $this->PDOStatement) {
+ E($this->error());
+ }
+ foreach ($bind as $key => $val) {
+ if (is_array($val)) {
+ $this->PDOStatement->bindValue($key, $val[0], $val[1]);
+ } else {
+ $this->PDOStatement->bindValue($key, $val);
+ }
+ }
+ $result = $this->PDOStatement->execute();
+ $this->debug(false);
+ if (false === $result) {
+ $this->error();
+ return false;
+ } else {
+ $this->numRows = $this->PDOStatement->rowCount();
+ if (preg_match("/^\s*(INSERT\s+INTO|REPLACE\s+INTO)\s+/i", $str)) {
+ $this->lastInsID = $this->_linkID->lastInsertId();
+ }
+ return $this->numRows;
+ }
+ }
+
+ /**
+ * 启动事务
+ * @access public
+ * @return void
+ */
+ public function startTrans()
+ {
+ $this->initConnect(true);
+ if (!$this->_linkID) {
+ return false;
+ }
+
+ //数据rollback 支持
+ if (0 == $this->transTimes) {
+ // 记录当前操作PDO
+ $this->transPdo = $this->_linkID;
+ $this->_linkID->beginTransaction();
+ }
+ $this->transTimes++;
+ return;
+ }
+
+ /**
+ * 用于非自动提交状态下面的查询提交
+ * @access public
+ * @return boolean
+ */
+ public function commit()
+ {
+ if ($this->transTimes == 1) {
+ // 由嵌套事物的最外层进行提交
+ $result = $this->_linkID->commit();
+ $this->transTimes = 0;
+ $this->transPdo = null;
+ if (!$result) {
+ $this->error();
+ return false;
+ }
+ } else {
+ $this->transTimes--;
+ }
+ return true;
+ }
+
+ /**
+ * 事务回滚
+ * @access public
+ * @return boolean
+ */
+ public function rollback()
+ {
+ if ($this->transTimes > 0) {
+ $result = $this->_linkID->rollback();
+ $this->transTimes = 0;
+ $this->transPdo = null;
+ if (!$result) {
+ $this->error();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 获得所有的查询数据
+ * @access private
+ * @return array
+ */
+ private function getResult()
+ {
+ //返回数据集
+ $result = $this->PDOStatement->fetchAll(PDO::FETCH_ASSOC);
+ $this->numRows = count($result);
+ return $result;
+ }
+
+ /**
+ * 获得查询次数
+ * @access public
+ * @param boolean $execute 是否包含所有查询
+ * @return integer
+ */
+ public function getQueryTimes($execute = false)
+ {
+ return $execute ? $this->queryTimes + $this->executeTimes : $this->queryTimes;
+ }
+
+ /**
+ * 获得执行次数
+ * @access public
+ * @return integer
+ */
+ public function getExecuteTimes()
+ {
+ return $this->executeTimes;
+ }
+
+ /**
+ * 关闭数据库
+ * @access public
+ */
+ public function close()
+ {
+ $this->_linkID = null;
+ }
+
+ /**
+ * 数据库错误信息
+ * 并显示当前的SQL语句
+ * @access public
+ * @return string
+ */
+ public function error()
+ {
+ if ($this->PDOStatement) {
+ $error = $this->PDOStatement->errorInfo();
+ $this->error = $error[1] . ':' . $error[2];
+ } else {
+ $this->error = '';
+ }
+ if ('' != $this->queryStr) {
+ $this->error .= "\n [ SQL语句 ] : " . $this->queryStr;
+ }
+ // 记录错误日志
+ trace($this->error, '', 'ERR');
+ if ($this->config['debug']) {
+ // 开启数据库调试模式
+ E($this->error);
+ } else {
+ return $this->error;
+ }
+ }
+
+ /**
+ * 获取最近一次查询的sql语句
+ * @param string $model 模型名
+ * @access public
+ * @return string
+ */
+ public function getLastSql($model = '')
+ {
+ return $model ? $this->modelSql[$model] : $this->queryStr;
+ }
+
+ /**
+ * 获取最近插入的ID
+ * @access public
+ * @return string
+ */
+ public function getLastInsID()
+ {
+ return $this->lastInsID;
+ }
+
+ /**
+ * 获取最近的错误信息
+ * @access public
+ * @return string
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * SQL指令安全过滤
+ * @access public
+ * @param string $str SQL字符串
+ * @return string
+ */
+ public function escapeString($str)
+ {
+ return addslashes($str);
+ }
+
+ /**
+ * 设置当前操作模型
+ * @access public
+ * @param string $model 模型名
+ * @return void
+ */
+ public function setModel($model)
+ {
+ $this->model = $model;
+ }
+
+ /**
+ * 数据库调试 记录当前SQL
+ * @access protected
+ * @param boolean $start 调试开始标记 true 开始 false 结束
+ */
+ protected function debug($start)
+ {
+ if ($this->config['debug']) {
+ // 开启数据库调试模式
+ if ($start) {
+ G('queryStartTime');
+ } else {
+ $this->modelSql[$this->model] = $this->queryStr;
+ //$this->model = '_think_';
+ // 记录操作结束时间
+ G('queryEndTime');
+ trace($this->queryStr . ' [ RunTime:' . G('queryStartTime', 'queryEndTime') . 's ]', '', 'SQL');
+ }
+ }
+ }
+
+ /**
+ * 初始化数据库连接
+ * @access protected
+ * @param boolean $master 主服务器
+ * @return void
+ */
+ protected function initConnect($master = true)
+ {
+ // 开启事物时用同一个连接进行操作
+ if ($this->transPDO) {
+ return $this->transPDO;
+ }
+
+ if (!empty($this->config['deploy']))
+ // 采用分布式数据库
+ {
+ $this->_linkID = $this->multiConnect($master);
+ } else
+ // 默认单数据库
+ if (!$this->_linkID) {
+ $this->_linkID = $this->connect();
+ }
+
+ }
+
+ /**
+ * 连接分布式服务器
+ * @access protected
+ * @param boolean $master 主服务器
+ * @return void
+ */
+ protected function multiConnect($master = false)
+ {
+ // 分布式数据库配置解析
+ $_config['username'] = explode(',', $this->config['username']);
+ $_config['password'] = explode(',', $this->config['password']);
+ $_config['hostname'] = explode(',', $this->config['hostname']);
+ $_config['hostport'] = explode(',', $this->config['hostport']);
+ $_config['database'] = explode(',', $this->config['database']);
+ $_config['dsn'] = explode(',', $this->config['dsn']);
+ $_config['charset'] = explode(',', $this->config['charset']);
+
+ // 数据库读写是否分离
+ if ($this->config['rw_separate']) {
+ // 主从式采用读写分离
+ if ($master)
+ // 主服务器写入
+ {
+ $r = floor(mt_rand(0, $this->config['master_num'] - 1));
+ } else {
+ if (is_numeric($this->config['slave_no'])) {
+// 指定服务器读
+ $r = $this->config['slave_no'];
+ } else {
+ // 读操作连接从服务器
+ $r = floor(mt_rand($this->config['master_num'], count($_config['hostname']) - 1)); // 每次随机连接的数据库
+ }
+ }
+ } else {
+ // 读写操作不区分服务器
+ $r = floor(mt_rand(0, count($_config['hostname']) - 1)); // 每次随机连接的数据库
+ }
+ $db_config = array(
+ 'username' => isset($_config['username'][$r]) ? $_config['username'][$r] : $_config['username'][0],
+ 'password' => isset($_config['password'][$r]) ? $_config['password'][$r] : $_config['password'][0],
+ 'hostname' => isset($_config['hostname'][$r]) ? $_config['hostname'][$r] : $_config['hostname'][0],
+ 'hostport' => isset($_config['hostport'][$r]) ? $_config['hostport'][$r] : $_config['hostport'][0],
+ 'database' => isset($_config['database'][$r]) ? $_config['database'][$r] : $_config['database'][0],
+ 'dsn' => isset($_config['dsn'][$r]) ? $_config['dsn'][$r] : $_config['dsn'][0],
+ 'charset' => isset($_config['charset'][$r]) ? $_config['charset'][$r] : $_config['charset'][0],
+ );
+ return $this->connect($db_config, $r);
+ }
+
+ /**
+ * 析构方法
+ * @access public
+ */
+ public function __destruct()
+ {
+ // 释放查询
+ if ($this->PDOStatement) {
+ $this->free();
+ }
+ // 关闭连接
+ $this->close();
+ }
+}
diff --git a/Framework/Library/Think/Dispatcher.class.php b/Framework/Library/Think/Dispatcher.class.php
new file mode 100644
index 00000000..8d8376b6
--- /dev/null
+++ b/Framework/Library/Think/Dispatcher.class.php
@@ -0,0 +1,408 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * ThinkPHP内置的Dispatcher类
+ * 完成URL解析、路由和调度
+ */
+class Dispatcher
+{
+
+ /**
+ * URL映射到控制器
+ * @access public
+ * @return void
+ */
+ public static function dispatch()
+ {
+ $varPath = C('VAR_PATHINFO');
+ $urlCase = C('URL_CASE_INSENSITIVE');
+ if (isset($_GET[$varPath])) { // 判断URL里面是否有兼容模式参数
+ $_SERVER['PATH_INFO'] = $_GET[$varPath];
+ unset($_GET[$varPath]);
+ } elseif (IS_CLI) { // CLI模式下 index.php module/controller/action/params/...
+ $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
+ }
+
+ // 开启子域名部署
+ if (C('APP_SUB_DOMAIN_DEPLOY')) {
+ $rules = C('APP_SUB_DOMAIN_RULES');
+ if (isset($rules[$_SERVER['HTTP_HOST']])) { // 完整域名或者IP配置
+ define('APP_DOMAIN', $_SERVER['HTTP_HOST']); // 当前完整域名
+ $rule = $rules[APP_DOMAIN];
+ } else {
+ if (strpos(C('APP_DOMAIN_SUFFIX'), '.')) { // com.cn net.cn
+ $domain = array_slice(explode('.', $_SERVER['HTTP_HOST']), 0, -3);
+ } else {
+ $domain = array_slice(explode('.', $_SERVER['HTTP_HOST']), 0, -2);
+ }
+ if (!empty($domain)) {
+ $subDomain = implode('.', $domain);
+ define('SUB_DOMAIN', $subDomain); // 当前完整子域名
+ $domain2 = array_pop($domain); // 二级域名
+ if ($domain) {
+ // 存在三级域名
+ $domain3 = array_pop($domain);
+ }
+ if (isset($rules[$subDomain])) {
+ // 子域名
+ $rule = $rules[$subDomain];
+ } elseif (isset($rules['*.' . $domain2]) && !empty($domain3)) {
+ // 泛三级域名
+ $rule = $rules['*.' . $domain2];
+ $panDomain = $domain3;
+ } elseif (isset($rules['*']) && !empty($domain2) && 'www' != $domain2) {
+ // 泛二级域名
+ $rule = $rules['*'];
+ $panDomain = $domain2;
+ }
+ }
+ }
+
+ if (!empty($rule)) {
+ // 子域名部署规则 '子域名'=>array('模块名[/控制器名]','var1=a&var2=b');
+ if (is_array($rule)) {
+ list($rule, $vars) = $rule;
+ }
+ $array = explode('/', $rule);
+ // 模块绑定
+ define('BIND_MODULE', array_shift($array));
+ // 控制器绑定
+ if (!empty($array)) {
+ $controller = array_shift($array);
+ if ($controller) {
+ define('BIND_CONTROLLER', $controller);
+ }
+ }
+ if (isset($vars)) {
+ // 传入参数
+ parse_str($vars, $parms);
+ if (isset($panDomain)) {
+ $pos = array_search('*', $parms);
+ if (false !== $pos) {
+ // 泛域名作为参数
+ $parms[$pos] = $panDomain;
+ }
+ }
+ $_GET = array_merge($_GET, $parms);
+ }
+ }
+ }
+ // 分析PATHINFO信息
+ if (!isset($_SERVER['PATH_INFO'])) {
+ $types = explode(',', C('URL_PATHINFO_FETCH'));
+ foreach ($types as $type) {
+ if (0 === strpos($type, ':')) {
+ // 支持函数判断
+ $_SERVER['PATH_INFO'] = call_user_func(substr($type, 1));
+ break;
+ } elseif (!empty($_SERVER[$type])) {
+ $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ?
+ substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type];
+ break;
+ }
+ }
+ }
+
+ $depr = C('URL_PATHINFO_DEPR');
+ define('MODULE_PATHINFO_DEPR', $depr);
+
+ if (empty($_SERVER['PATH_INFO'])) {
+ $_SERVER['PATH_INFO'] = '';
+ define('__INFO__', '');
+ define('__EXT__', '');
+ $paths = array();
+ } else {
+ // URL后缀
+ define('__EXT__', strtolower(pathinfo($_SERVER['PATH_INFO'], PATHINFO_EXTENSION)));
+ // 检查禁止访问的URL后缀
+ if ($denySuffix = C('URL_DENY_SUFFIX')) {
+ if (in_array(__EXT__, explode('|', strtolower(str_replace('.', '', $denySuffix))))) {
+ send_http_status(404);
+ exit;
+ }
+ }
+ define('__INFO__', trim($_SERVER['PATH_INFO'], '/'));
+ // 去除URL后缀
+ $_SERVER['PATH_INFO'] = preg_replace('/\.' . __EXT__ . '$/i', '', __INFO__);
+ $paths = explode($depr, trim($_SERVER['PATH_INFO'], $depr));
+ }
+
+ // URL常量
+ define('__SELF__', strip_tags($_SERVER[C('URL_REQUEST_URI')]));
+
+ // 获取模块名称
+ define('MODULE_NAME', self::getModule($paths));
+
+ // 检测模块是否存在
+ if (MODULE_NAME && is_dir(APP_PATH . MODULE_NAME)) {
+ // 定义当前模块路径
+ define('MODULE_PATH', APP_PATH . MODULE_NAME . '/');
+ // 定义当前模块的模版缓存路径
+ C('CACHE_PATH', CACHE_PATH . MODULE_NAME . '/');
+ // 定义当前模块的日志目录
+ C('LOG_PATH', realpath(LOG_PATH) . '/' . MODULE_NAME . '/');
+
+ // 模块配置文件开始载入检测位
+ Hook::listen('module_check');
+
+ // 加载模块配置文件
+ if (is_file(MODULE_PATH . 'Conf/config' . CONF_EXT)) {
+ C(load_config(MODULE_PATH . 'Conf/config' . CONF_EXT));
+ }
+
+ // 加载应用模式对应的配置文件
+ if ('common' != APP_MODE && is_file(MODULE_PATH . 'Conf/config_' . APP_MODE . CONF_EXT)) {
+ C(load_config(MODULE_PATH . 'Conf/config_' . APP_MODE . CONF_EXT));
+ }
+
+ // 当前应用状态对应的配置文件
+ if (APP_STATUS && is_file(MODULE_PATH . 'Conf/' . APP_STATUS . CONF_EXT)) {
+ C(load_config(MODULE_PATH . 'Conf/' . APP_STATUS . CONF_EXT));
+ }
+
+ // 加载模块别名定义
+ if (is_file(MODULE_PATH . 'Conf/alias.php')) {
+ Think::addMap(include MODULE_PATH . 'Conf/alias.php');
+ }
+
+ // 加载模块tags文件定义
+ if (is_file(MODULE_PATH . 'Conf/tags.php')) {
+ Hook::import(include MODULE_PATH . 'Conf/tags.php');
+ }
+
+ // 加载模块函数文件
+ if (is_file(MODULE_PATH . 'Common/function.php'))
+ include MODULE_PATH . 'Common/function.php';
+ // 加载模块的扩展配置文件
+ load_ext_file(MODULE_PATH);
+
+ // 模块配置文件加载完成检测位
+ Hook::listen('module_config');
+ } else {
+ E(L('_MODULE_NOT_EXIST_') . ':' . MODULE_NAME);
+ }
+
+ if (!defined('__APP__')) {
+ $urlMode = C('URL_MODEL');
+ if (URL_COMPAT == $urlMode) {
+ // 兼容模式判断
+ define('PHP_FILE', _PHP_FILE_ . '?' . $varPath . '=');
+ } elseif (URL_REWRITE == $urlMode) {
+ $url = dirname(_PHP_FILE_);
+ if ('/' == $url || '\\' == $url) {
+ $url = '';
+ }
+
+ define('PHP_FILE', $url);
+ } else {
+ define('PHP_FILE', _PHP_FILE_);
+ }
+ // 当前应用地址
+ define('__APP__', strip_tags(PHP_FILE));
+ }
+ // 模块URL地址
+ $moduleName = defined('MODULE_ALIAS') ? MODULE_ALIAS : MODULE_NAME;
+ define('__MODULE__', (defined('BIND_MODULE') || !C('MULTI_MODULE')) ? __APP__ : __APP__ . '/' . ($urlCase ? strtolower($moduleName) : $moduleName));
+ // 获取控制器和操作名
+ define('CONTROLLER_NAME', self::getController($paths, $urlCase));
+ define('ACTION_NAME', self::getAction($paths, $urlCase));
+
+ if ($paths) {
+ // 解析剩余的URL参数
+ $var = array();
+ if (C('URL_PARAMS_BIND') && 1 == C('URL_PARAMS_BIND_TYPE')) {
+ // URL参数按顺序绑定变量
+ $var = $paths;
+ } else {
+ preg_replace_callback('/(\w+)\/([^\/]+)/', function ($match) use (&$var) {
+ $var[$match[1]] = strip_tags($match[2]);
+ }, implode('/', $paths));
+ }
+ $_GET = array_merge($var, $_GET);
+ }
+ // 获取控制器的命名空间(路径)
+ define('CONTROLLER_PATH', self::getSpace($urlCase));
+
+ // 当前控制器的UR地址
+ $controllerName = defined('CONTROLLER_ALIAS') ? CONTROLLER_ALIAS : CONTROLLER_NAME;
+ define('__CONTROLLER__', __MODULE__ . $depr . (defined('BIND_CONTROLLER') ? '' : ($urlCase ? parse_name($controllerName) : $controllerName)));
+
+ // 当前操作的URL地址
+ define('__ACTION__', __CONTROLLER__ . $depr . (defined('ACTION_ALIAS') ? ACTION_ALIAS : ACTION_NAME));
+
+ //保证$_REQUEST正常取值
+ $_REQUEST = array_merge($_POST, $_GET);
+ }
+
+ /**
+ * 获得控制器的命名空间路径 便于插件机制访问
+ * @param boolean $urlCase 是否转换成小写
+ * @return string
+ */
+ private static function getSpace($urlCase)
+ {
+ $var = C('VAR_ADDON');
+ $space = !empty($_GET[$var]) ? strip_tags($_GET[$var]) : '';
+ unset($_GET[$var]);
+ return $space;
+ }
+
+ /**
+ * 获得实际的控制器名称
+ * @param array $paths path_info数组
+ * @param boolean $urlCase 是否转换成小写
+ * @return string
+ */
+ private static function getController(&$paths, $urlCase)
+ {
+ if (defined('BIND_CONTROLLER')) {
+ return BIND_CONTROLLER;
+ } else {
+ if ($paths && C('URL_ROUTER_ON') && Route::check($paths)) {
+ $depr = C('URL_PATHINFO_DEPR');
+ $paths = explode($depr, trim($_SERVER['PATH_INFO'], $depr));
+ }
+ if ($paths) {
+ // PATH_INFO检测标签位
+ Hook::listen('path_info');
+ if (C('CONTROLLER_LEVEL') > 1) {// 控制器层次
+ $controller = implode('/', array_slice($paths, 0, C('CONTROLLER_LEVEL')));
+ $paths = array_slice($paths, C('CONTROLLER_LEVEL'));
+ } else {
+ $controller = array_shift($paths);
+ }
+ } else {
+ $var = C('VAR_CONTROLLER');
+ if (!empty($_GET[$var])) {
+ $controller = $_GET[$var];
+ unset($_GET[$var]);
+ } else {
+ $controller = C('DEFAULT_CONTROLLER');
+ }
+ }
+ }
+ if ($maps = C('URL_CONTROLLER_MAP')) {
+ if (isset($maps[strtolower($controller)])) {
+ // 记录当前别名
+ define('CONTROLLER_ALIAS', strtolower($controller));
+ // 获取实际的控制器名
+ return ucfirst($maps[CONTROLLER_ALIAS]);
+ } elseif (array_search(strtolower($controller), $maps)) {
+ // 禁止访问原始控制器
+ return '';
+ }
+ }
+ if ($urlCase) {
+ // URL地址不区分大小写
+ // 智能识别方式 user_type 识别到 UserTypeController 控制器
+ $controller = parse_name($controller, 1);
+ }
+ return strip_tags(ucfirst($controller));
+ }
+
+ /**
+ * 获得实际的操作名称
+ * @param array $paths path_info数组
+ * @param boolean $urlCase 是否转换成小写
+ * @return string
+ */
+ private static function getAction(&$paths, $urlCase)
+ {
+ if (defined('BIND_ACTION')) {
+ return BIND_ACTION;
+ } else {
+ if ($paths) {
+ $action = array_shift($paths);
+ } else {
+ $var = C('VAR_ACTION');
+ if (!empty($_GET[$var])) {
+ $action = $_GET[$var];
+ unset($_GET[$var]);
+ } elseif (!empty($_POST[$var])) {
+ $action = $_POST[$var];
+ unset($_POST[$var]);
+ } else {
+ $action = C('DEFAULT_ACTION');
+ }
+ }
+ }
+ if ($maps = C('URL_ACTION_MAP')) {
+ if (isset($maps[strtolower(CONTROLLER_NAME)])) {
+ $maps = $maps[strtolower(CONTROLLER_NAME)];
+ if (isset($maps[strtolower($action)])) {
+ // 记录当前别名
+ define('ACTION_ALIAS', strtolower($action));
+ // 获取实际的操作名
+ if (is_array($maps[ACTION_ALIAS])) {
+ parse_str($maps[ACTION_ALIAS][1], $vars);
+ $_GET = array_merge($_GET, $vars);
+ return $maps[ACTION_ALIAS][0];
+ } else {
+ return $maps[ACTION_ALIAS];
+ }
+
+ } elseif (array_search(strtolower($action), $maps)) {
+ // 禁止访问原始操作
+ return '';
+ }
+ }
+ }
+ return strip_tags($urlCase ? strtolower($action) : $action);
+ }
+
+ /**
+ * 获得实际的模块名称
+ * @param array $paths path_info数组
+ * @return string
+ */
+ private static function getModule(&$paths)
+ {
+ if (defined('BIND_MODULE')) {
+ return BIND_MODULE;
+ } else {
+ // 检查路由
+ if ($paths && C('URL_ROUTER_ON') && Route::check($paths)) {
+ $paths = explode(MODULE_PATHINFO_DEPR, trim($_SERVER['PATH_INFO'], MODULE_PATHINFO_DEPR));
+ }
+ if ($paths && C('MULTI_MODULE')) { // 获取模块名
+ $allowList = C('MODULE_ALLOW_LIST'); // 允许的模块列表
+ if (empty($allowList) || (is_array($allowList) && in_array_case($paths[0], $allowList))) {
+ $module = array_shift($paths);
+ $_SERVER['PATH_INFO'] = implode(MODULE_PATHINFO_DEPR, $paths);
+ }
+ } else {
+ $var = C('VAR_MODULE');
+ if (!empty($_GET[$var])) {
+ $module = $_GET[$var];
+ unset($_GET[$var]);
+ }
+ }
+ if (empty($module)) {
+ $module = C('DEFAULT_MODULE');
+ }
+ }
+ if ($maps = C('URL_MODULE_MAP')) {
+ if (isset($maps[strtolower($module)])) {
+ // 记录当前别名
+ define('MODULE_ALIAS', strtolower($module));
+ // 获取实际的模块名
+ return ucfirst($maps[MODULE_ALIAS]);
+ } elseif (array_search(strtolower($module), $maps) || in_array_case($module, C('MODULE_DENY_LIST'))) {
+ // 禁止访问原始模块
+ return '';
+ }
+ }
+ return strip_tags(ucfirst($module));
+ }
+
+}
diff --git a/Framework/Library/Think/Exception.class.php b/Framework/Library/Think/Exception.class.php
new file mode 100644
index 00000000..16fa2797
--- /dev/null
+++ b/Framework/Library/Think/Exception.class.php
@@ -0,0 +1,18 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * ThinkPHP系统异常基类
+ */
+class Exception extends \Exception
+{
+}
diff --git a/Framework/Library/Think/Hook.class.php b/Framework/Library/Think/Hook.class.php
new file mode 100644
index 00000000..cfee1a74
--- /dev/null
+++ b/Framework/Library/Think/Hook.class.php
@@ -0,0 +1,133 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * ThinkPHP系统钩子实现
+ */
+class Hook
+{
+
+ private static $tags = array();
+
+ /**
+ * 动态添加插件到某个标签
+ * @param string $tag 标签名称
+ * @param mixed $name 插件名称
+ * @return void
+ */
+ public static function add($tag, $name)
+ {
+ if (!isset(self::$tags[$tag])) {
+ self::$tags[$tag] = array();
+ }
+ if (is_array($name)) {
+ self::$tags[$tag] = array_merge(self::$tags[$tag], $name);
+ } else {
+ self::$tags[$tag][] = $name;
+ }
+ }
+
+ /**
+ * 批量导入插件
+ * @param array $data 插件信息
+ * @param boolean $recursive 是否递归合并
+ * @return void
+ */
+ public static function import($data, $recursive = true)
+ {
+ if (!$recursive) {
+ // 覆盖导入
+ self::$tags = array_merge(self::$tags, $data);
+ } else {
+ // 合并导入
+ foreach ($data as $tag => $val) {
+ if (!isset(self::$tags[$tag])) {
+ self::$tags[$tag] = array();
+ }
+
+ if (!empty($val['_overlay'])) {
+ // 可以针对某个标签指定覆盖模式
+ unset($val['_overlay']);
+ self::$tags[$tag] = $val;
+ } else {
+ // 合并模式
+ self::$tags[$tag] = array_merge(self::$tags[$tag], $val);
+ }
+ }
+ }
+ }
+
+ /**
+ * 获取插件信息
+ * @param string $tag 插件位置 留空获取全部
+ * @return array
+ */
+ public static function get($tag = '')
+ {
+ if (empty($tag)) {
+ // 获取全部的插件信息
+ return self::$tags;
+ } else {
+ return self::$tags[$tag];
+ }
+ }
+
+ /**
+ * 监听标签的插件
+ * @param string $tag 标签名称
+ * @param mixed $params 传入参数
+ * @return void
+ */
+ public static function listen($tag, &$params = null)
+ {
+ if (isset(self::$tags[$tag])) {
+ if (APP_DEBUG) {
+ G($tag . 'Start');
+ trace('[ ' . $tag . ' ] --START--', '', 'INFO');
+ }
+ foreach (self::$tags[$tag] as $name) {
+ APP_DEBUG && G($name . '_start');
+ $result = self::exec($name, $tag, $params);
+ if (APP_DEBUG) {
+ G($name . '_end');
+ trace('Run ' . $name . ' [ RunTime:' . G($name . '_start', $name . '_end', 6) . 's ]', '', 'INFO');
+ }
+ if (false === $result) {
+ // 如果返回false 则中断插件执行
+ return;
+ }
+ }
+ if (APP_DEBUG) {
+ // 记录行为的执行日志
+ trace('[ ' . $tag . ' ] --END-- [ RunTime:' . G($tag . 'Start', $tag . 'End', 6) . 's ]', '', 'INFO');
+ }
+ }
+ return;
+ }
+
+ /**
+ * 执行某个插件
+ * @param string $name 插件名称
+ * @param string $tag 方法名(标签名)
+ * @param Mixed $params 传入的参数
+ * @return void
+ */
+ public static function exec($name, $tag, &$params = null)
+ {
+ if ('Behavior' == substr($name, -8)) {
+ // 行为扩展必须用run入口方法
+ $tag = 'run';
+ }
+ $addon = new $name();
+ return $addon->$tag($params);
+ }
+}
diff --git a/Framework/Library/Think/Image.class.php b/Framework/Library/Think/Image.class.php
new file mode 100644
index 00000000..1c5fabb9
--- /dev/null
+++ b/Framework/Library/Think/Image.class.php
@@ -0,0 +1,203 @@
+
+// +----------------------------------------------------------------------
+// | ThinkImage.class.php 2013-03-05
+// +----------------------------------------------------------------------
+
+namespace Think;
+
+/**
+ * 图片处理驱动类,可配置图片处理库
+ * 目前支持GD库和imagick
+ * @author 麦当苗儿
+ */
+class Image
+{
+ /* 驱动相关常量定义 */
+ const IMAGE_GD = 1; //常量,标识GD库类型
+ const IMAGE_IMAGICK = 2; //常量,标识imagick库类型
+
+ /* 缩略图相关常量定义 */
+ const IMAGE_THUMB_SCALE = 1; //常量,标识缩略图等比例缩放类型
+ const IMAGE_THUMB_FILLED = 2; //常量,标识缩略图缩放后填充类型
+ const IMAGE_THUMB_CENTER = 3; //常量,标识缩略图居中裁剪类型
+ const IMAGE_THUMB_NORTHWEST = 4; //常量,标识缩略图左上角裁剪类型
+ const IMAGE_THUMB_SOUTHEAST = 5; //常量,标识缩略图右下角裁剪类型
+ const IMAGE_THUMB_FIXED = 6; //常量,标识缩略图固定尺寸缩放类型
+
+ /* 水印相关常量定义 */
+ const IMAGE_WATER_NORTHWEST = 1; //常量,标识左上角水印
+ const IMAGE_WATER_NORTH = 2; //常量,标识上居中水印
+ const IMAGE_WATER_NORTHEAST = 3; //常量,标识右上角水印
+ const IMAGE_WATER_WEST = 4; //常量,标识左居中水印
+ const IMAGE_WATER_CENTER = 5; //常量,标识居中水印
+ const IMAGE_WATER_EAST = 6; //常量,标识右居中水印
+ const IMAGE_WATER_SOUTHWEST = 7; //常量,标识左下角水印
+ const IMAGE_WATER_SOUTH = 8; //常量,标识下居中水印
+ const IMAGE_WATER_SOUTHEAST = 9; //常量,标识右下角水印
+
+ /**
+ * 图片资源
+ * @var resource
+ */
+ private $img;
+
+ /**
+ * 构造方法,用于实例化一个图片处理对象
+ * @param string $type 要使用的类库,默认使用GD库
+ */
+ public function __construct($type = self::IMAGE_GD, $imgname = null)
+ {
+ /* 判断调用库的类型 */
+ switch ($type) {
+ case self::IMAGE_GD:
+ $class = 'Gd';
+ break;
+ case self::IMAGE_IMAGICK:
+ $class = 'Imagick';
+ break;
+ default:
+ E('不支持的图片处理库类型');
+ }
+
+ /* 引入处理库,实例化图片处理对象 */
+ $class = "Think\\Image\\Driver\\{$class}";
+ $this->img = new $class($imgname);
+ }
+
+ /**
+ * 打开一幅图像
+ * @param string $imgname 图片路径
+ * @return Object 当前图片处理库对象
+ */
+ public function open($imgname)
+ {
+ $this->img->open($imgname);
+ return $this;
+ }
+
+ /**
+ * 保存图片
+ * @param string $imgname 图片保存名称
+ * @param string $type 图片类型
+ * @param integer $quality 图像质量
+ * @param boolean $interlace 是否对JPEG类型图片设置隔行扫描
+ * @return Object 当前图片处理库对象
+ */
+ public function save($imgname, $type = null, $quality = 80, $interlace = true)
+ {
+ $this->img->save($imgname, $type, $quality, $interlace);
+ return $this;
+ }
+
+ /**
+ * 返回图片宽度
+ * @return integer 图片宽度
+ */
+ public function width()
+ {
+ return $this->img->width();
+ }
+
+ /**
+ * 返回图片高度
+ * @return integer 图片高度
+ */
+ public function height()
+ {
+ return $this->img->height();
+ }
+
+ /**
+ * 返回图像类型
+ * @return string 图片类型
+ */
+ public function type()
+ {
+ return $this->img->type();
+ }
+
+ /**
+ * 返回图像MIME类型
+ * @return string 图像MIME类型
+ */
+ public function mime()
+ {
+ return $this->img->mime();
+ }
+
+ /**
+ * 返回图像尺寸数组 0 - 图片宽度,1 - 图片高度
+ * @return array 图片尺寸
+ */
+ public function size()
+ {
+ return $this->img->size();
+ }
+
+ /**
+ * 裁剪图片
+ * @param integer $w 裁剪区域宽度
+ * @param integer $h 裁剪区域高度
+ * @param integer $x 裁剪区域x坐标
+ * @param integer $y 裁剪区域y坐标
+ * @param integer $width 图片保存宽度
+ * @param integer $height 图片保存高度
+ * @return Object 当前图片处理库对象
+ */
+ public function crop($w, $h, $x = 0, $y = 0, $width = null, $height = null)
+ {
+ $this->img->crop($w, $h, $x, $y, $width, $height);
+ return $this;
+ }
+
+ /**
+ * 生成缩略图
+ * @param integer $width 缩略图最大宽度
+ * @param integer $height 缩略图最大高度
+ * @param integer $type 缩略图裁剪类型
+ * @return Object 当前图片处理库对象
+ */
+ public function thumb($width, $height, $type = self::IMAGE_THUMB_SCALE)
+ {
+ $this->img->thumb($width, $height, $type);
+ return $this;
+ }
+
+ /**
+ * 添加水印
+ * @param string $source 水印图片路径
+ * @param integer $locate 水印位置
+ * @param integer $alpha 水印透明度
+ * @return Object 当前图片处理库对象
+ */
+ public function water($source, $locate = self::IMAGE_WATER_SOUTHEAST, $alpha = 80)
+ {
+ $this->img->water($source, $locate, $alpha);
+ return $this;
+ }
+
+ /**
+ * 图像添加文字
+ * @param string $text 添加的文字
+ * @param string $font 字体路径
+ * @param integer $size 字号
+ * @param string $color 文字颜色
+ * @param integer $locate 文字写入位置
+ * @param integer $offset 文字相对当前位置的偏移量
+ * @param integer $angle 文字倾斜角度
+ * @return Object 当前图片处理库对象
+ */
+ public function text($text, $font, $size, $color = '#00000000',
+ $locate = self::IMAGE_WATER_SOUTHEAST, $offset = 0, $angle = 0) {
+ $this->img->text($text, $font, $size, $color, $locate, $offset, $angle);
+ return $this;
+ }
+}
diff --git a/Framework/Library/Think/Image/Driver/GIF.class.php b/Framework/Library/Think/Image/Driver/GIF.class.php
new file mode 100644
index 00000000..d39ccbdb
--- /dev/null
+++ b/Framework/Library/Think/Image/Driver/GIF.class.php
@@ -0,0 +1,579 @@
+
+// +----------------------------------------------------------------------
+// | GIF.class.php 2013-03-09
+// +----------------------------------------------------------------------
+namespace Think\Image\Driver;
+
+class GIF
+{
+ /**
+ * GIF帧列表
+ * @var array
+ */
+ private $frames = array();
+
+ /**
+ * 每帧等待时间列表
+ * @var array
+ */
+ private $delays = array();
+
+ /**
+ * 构造方法,用于解码GIF图片
+ * @param string $src GIF图片数据
+ * @param string $mod 图片数据类型
+ */
+ public function __construct($src = null, $mod = 'url')
+ {
+ if (!is_null($src)) {
+ if ('url' == $mod && is_file($src)) {
+ $src = file_get_contents($src);
+ }
+
+ /* 解码GIF图片 */
+ try {
+ $de = new GIFDecoder($src);
+ $this->frames = $de->GIFGetFrames();
+ $this->delays = $de->GIFGetDelays();
+ } catch (\Exception $e) {
+ E("解码GIF图片出错");
+ }
+ }
+ }
+
+ /**
+ * 设置或获取当前帧的数据
+ * @param string $stream 二进制数据流
+ * @return boolean 获取到的数据
+ */
+ public function image($stream = null)
+ {
+ if (is_null($stream)) {
+ $current = current($this->frames);
+ return false === $current ? reset($this->frames) : $current;
+ } else {
+ $this->frames[key($this->frames)] = $stream;
+ }
+ }
+
+ /**
+ * 将当前帧移动到下一帧
+ * @return string 当前帧数据
+ */
+ public function nextImage()
+ {
+ return next($this->frames);
+ }
+
+ /**
+ * 编码并保存当前GIF图片
+ * @param string $gifname 图片名称
+ */
+ public function save($gifname)
+ {
+ $gif = new GIFEncoder($this->frames, $this->delays, 0, 2, 0, 0, 0, 'bin');
+ file_put_contents($gifname, $gif->GetAnimation());
+ }
+
+}
+
+/*
+:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::
+:: GIFEncoder Version 2.0 by László Zsidi, http://gifs.hu
+::
+:: This class is a rewritten 'GifMerge.class.php' version.
+::
+:: Modification:
+:: - Simplified and easy code,
+:: - Ultra fast encoding,
+:: - Built-in errors,
+:: - Stable working
+::
+::
+:: Updated at 2007. 02. 13. '00.05.AM'
+::
+::
+::
+:: Try on-line GIFBuilder Form demo based on GIFEncoder.
+::
+:: http://gifs.hu/phpclasses/demos/GifBuilder/
+::
+:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+ */
+
+class GIFEncoder
+{
+ private $GIF = "GIF89a"; /* GIF header 6 bytes */
+ private $VER = "GIFEncoder V2.05"; /* Encoder version */
+
+ private $BUF = array();
+ private $LOP = 0;
+ private $DIS = 2;
+ private $COL = -1;
+ private $IMG = -1;
+
+ private $ERR = array(
+ 'ERR00' => "Does not supported function for only one image!",
+ 'ERR01' => "Source is not a GIF image!",
+ 'ERR02' => "Unintelligible flag ",
+ 'ERR03' => "Does not make animation from animated GIF source",
+ );
+
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFEncoder...
+ ::
+ */
+ public function __construct($GIF_src, $GIF_dly, $GIF_lop, $GIF_dis, $GIF_red, $GIF_grn, $GIF_blu, $GIF_mod)
+ {
+ if (!is_array($GIF_src) && !is_array($GIF_dly)) {
+ printf("%s: %s", $this->VER, $this->ERR['ERR00']);
+ exit(0);
+ }
+ $this->LOP = ($GIF_lop > -1) ? $GIF_lop : 0;
+ $this->DIS = ($GIF_dis > -1) ? (($GIF_dis < 3) ? $GIF_dis : 3) : 2;
+ $this->COL = ($GIF_red > -1 && $GIF_grn > -1 && $GIF_blu > -1) ?
+ ($GIF_red | ($GIF_grn << 8) | ($GIF_blu << 16)) : -1;
+
+ for ($i = 0; $i < count($GIF_src); $i++) {
+ if (strToLower($GIF_mod) == "url") {
+ $this->BUF[] = fread(fopen($GIF_src[$i], "rb"), filesize($GIF_src[$i]));
+ } else if (strToLower($GIF_mod) == "bin") {
+ $this->BUF[] = $GIF_src[$i];
+ } else {
+ printf("%s: %s ( %s )!", $this->VER, $this->ERR['ERR02'], $GIF_mod);
+ exit(0);
+ }
+ if (substr($this->BUF[$i], 0, 6) != "GIF87a" && substr($this->BUF[$i], 0, 6) != "GIF89a") {
+ printf("%s: %d %s", $this->VER, $i, $this->ERR['ERR01']);
+ exit(0);
+ }
+ for ($j = (13 + 3 * (2 << (ord($this->BUF[$i]{10}) & 0x07))), $k = true; $k; $j++) {
+ switch ($this->BUF[$i]{ $j}) {
+ case "!":
+ if ((substr($this->BUF[$i], ($j + 3), 8)) == "NETSCAPE") {
+ printf("%s: %s ( %s source )!", $this->VER, $this->ERR['ERR03'], ($i + 1));
+ exit(0);
+ }
+ break;
+ case ";":
+ $k = false;
+ break;
+ }
+ }
+ }
+ $this->GIFAddHeader();
+ for ($i = 0; $i < count($this->BUF); $i++) {
+ $this->GIFAddFrames($i, $GIF_dly[$i]);
+ }
+ $this->GIFAddFooter();
+ }
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFAddHeader...
+ ::
+ */
+ private function GIFAddHeader()
+ {
+ $cmap = 0;
+
+ if (ord($this->BUF[0]{10}) & 0x80) {
+ $cmap = 3 * (2 << (ord($this->BUF[0]{10}) & 0x07));
+
+ $this->GIF .= substr($this->BUF[0], 6, 7);
+ $this->GIF .= substr($this->BUF[0], 13, $cmap);
+ $this->GIF .= "!\377\13NETSCAPE2.0\3\1" . $this->GIFWord($this->LOP) . "\0";
+ }
+ }
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFAddFrames...
+ ::
+ */
+ private function GIFAddFrames($i, $d)
+ {
+
+ $Locals_str = 13 + 3 * (2 << (ord($this->BUF[$i]{10}) & 0x07));
+
+ $Locals_end = strlen($this->BUF[$i]) - $Locals_str - 1;
+ $Locals_tmp = substr($this->BUF[$i], $Locals_str, $Locals_end);
+
+ $Global_len = 2 << (ord($this->BUF[0]{10}) & 0x07);
+ $Locals_len = 2 << (ord($this->BUF[$i]{10}) & 0x07);
+
+ $Global_rgb = substr($this->BUF[0], 13,
+ 3 * (2 << (ord($this->BUF[0]{10}) & 0x07)));
+ $Locals_rgb = substr($this->BUF[$i], 13,
+ 3 * (2 << (ord($this->BUF[$i]{10}) & 0x07)));
+
+ $Locals_ext = "!\xF9\x04" . chr(($this->DIS << 2) + 0) .
+ chr(($d >> 0) & 0xFF) . chr(($d >> 8) & 0xFF) . "\x0\x0";
+
+ if ($this->COL > -1 && ord($this->BUF[$i]{10}) & 0x80) {
+ for ($j = 0; $j < (2 << (ord($this->BUF[$i]{10}) & 0x07)); $j++) {
+ if (
+ ord($Locals_rgb{3 * $j + 0}) == (($this->COL >> 16) & 0xFF) &&
+ ord($Locals_rgb{3 * $j + 1}) == (($this->COL >> 8) & 0xFF) &&
+ ord($Locals_rgb{3 * $j + 2}) == (($this->COL >> 0) & 0xFF)
+ ) {
+ $Locals_ext = "!\xF9\x04" . chr(($this->DIS << 2) + 1) .
+ chr(($d >> 0) & 0xFF) . chr(($d >> 8) & 0xFF) . chr($j) . "\x0";
+ break;
+ }
+ }
+ }
+ switch ($Locals_tmp{0}) {
+ case "!":
+ $Locals_img = substr($Locals_tmp, 8, 10);
+ $Locals_tmp = substr($Locals_tmp, 18, strlen($Locals_tmp) - 18);
+ break;
+ case ",":
+ $Locals_img = substr($Locals_tmp, 0, 10);
+ $Locals_tmp = substr($Locals_tmp, 10, strlen($Locals_tmp) - 10);
+ break;
+ }
+ if (ord($this->BUF[$i]{10}) & 0x80 && $this->IMG > -1) {
+ if ($Global_len == $Locals_len) {
+ if ($this->GIFBlockCompare($Global_rgb, $Locals_rgb, $Global_len)) {
+ $this->GIF .= ($Locals_ext . $Locals_img . $Locals_tmp);
+ } else {
+ $byte = ord($Locals_img{9});
+ $byte |= 0x80;
+ $byte &= 0xF8;
+ $byte |= (ord($this->BUF[0]{10}) & 0x07);
+ $Locals_img{9} = chr($byte);
+ $this->GIF .= ($Locals_ext . $Locals_img . $Locals_rgb . $Locals_tmp);
+ }
+ } else {
+ $byte = ord($Locals_img{9});
+ $byte |= 0x80;
+ $byte &= 0xF8;
+ $byte |= (ord($this->BUF[$i]{10}) & 0x07);
+ $Locals_img{9} = chr($byte);
+ $this->GIF .= ($Locals_ext . $Locals_img . $Locals_rgb . $Locals_tmp);
+ }
+ } else {
+ $this->GIF .= ($Locals_ext . $Locals_img . $Locals_tmp);
+ }
+ $this->IMG = 1;
+ }
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFAddFooter...
+ ::
+ */
+ private function GIFAddFooter()
+ {
+ $this->GIF .= ";";
+ }
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFBlockCompare...
+ ::
+ */
+ private function GIFBlockCompare($GlobalBlock, $LocalBlock, $Len)
+ {
+
+ for ($i = 0; $i < $Len; $i++) {
+ if (
+ $GlobalBlock{3 * $i + 0} != $LocalBlock{3 * $i + 0} ||
+ $GlobalBlock{3 * $i + 1} != $LocalBlock{3 * $i + 1} ||
+ $GlobalBlock{3 * $i + 2} != $LocalBlock{3 * $i + 2}
+ ) {
+ return (0);
+ }
+ }
+
+ return (1);
+ }
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFWord...
+ ::
+ */
+ private function GIFWord($int)
+ {
+
+ return (chr($int & 0xFF) . chr(($int >> 8) & 0xFF));
+ }
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GetAnimation...
+ ::
+ */
+ public function GetAnimation()
+ {
+ return ($this->GIF);
+ }
+}
+
+/*
+:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::
+:: GIFDecoder Version 2.0 by László Zsidi, http://gifs.hu
+::
+:: Created at 2007. 02. 01. '07.47.AM'
+::
+::
+::
+::
+:: Try on-line GIFBuilder Form demo based on GIFDecoder.
+::
+:: http://gifs.hu/phpclasses/demos/GifBuilder/
+::
+:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+ */
+
+class GIFDecoder
+{
+ private $GIF_buffer = array();
+ private $GIF_arrays = array();
+ private $GIF_delays = array();
+ private $GIF_stream = "";
+ private $GIF_string = "";
+ private $GIF_bfseek = 0;
+
+ private $GIF_screen = array();
+ private $GIF_global = array();
+ private $GIF_sorted;
+ private $GIF_colorS;
+ private $GIF_colorC;
+ private $GIF_colorF;
+
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFDecoder ( $GIF_pointer )
+ ::
+ */
+ public function __construct($GIF_pointer)
+ {
+ $this->GIF_stream = $GIF_pointer;
+
+ $this->GIFGetByte(6); // GIF89a
+ $this->GIFGetByte(7); // Logical Screen Descriptor
+
+ $this->GIF_screen = $this->GIF_buffer;
+ $this->GIF_colorF = $this->GIF_buffer[4] & 0x80 ? 1 : 0;
+ $this->GIF_sorted = $this->GIF_buffer[4] & 0x08 ? 1 : 0;
+ $this->GIF_colorC = $this->GIF_buffer[4] & 0x07;
+ $this->GIF_colorS = 2 << $this->GIF_colorC;
+
+ if (1 == $this->GIF_colorF) {
+ $this->GIFGetByte(3 * $this->GIF_colorS);
+ $this->GIF_global = $this->GIF_buffer;
+ }
+ /*
+ *
+ * 05.06.2007.
+ * Made a little modification
+ *
+ *
+ - for ( $cycle = 1; $cycle; ) {
+ + if ( GIFDecoder::GIFGetByte ( 1 ) ) {
+ - switch ( $this->GIF_buffer [ 0 ] ) {
+ - case 0x21:
+ - GIFDecoder::GIFReadExtensions ( );
+ - break;
+ - case 0x2C:
+ - GIFDecoder::GIFReadDescriptor ( );
+ - break;
+ - case 0x3B:
+ - $cycle = 0;
+ - break;
+ - }
+ - }
+ + else {
+ + $cycle = 0;
+ + }
+ - }
+ */
+ for ($cycle = 1; $cycle;) {
+ if ($this->GIFGetByte(1)) {
+ switch ($this->GIF_buffer[0]) {
+ case 0x21:
+ $this->GIFReadExtensions();
+ break;
+ case 0x2C:
+ $this->GIFReadDescriptor();
+ break;
+ case 0x3B:
+ $cycle = 0;
+ break;
+ }
+ } else {
+ $cycle = 0;
+ }
+ }
+ }
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFReadExtension ( )
+ ::
+ */
+ private function GIFReadExtensions()
+ {
+ $this->GIFGetByte(1);
+ for (;;) {
+ $this->GIFGetByte(1);
+ if (($u = $this->GIF_buffer[0]) == 0x00) {
+ break;
+ }
+ $this->GIFGetByte($u);
+ /*
+ * 07.05.2007.
+ * Implemented a new line for a new function
+ * to determine the originaly delays between
+ * frames.
+ *
+ */
+ if (4 == $u) {
+ $this->GIF_delays[] = ($this->GIF_buffer[1] | $this->GIF_buffer[2] << 8);
+ }
+ }
+ }
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFReadExtension ( )
+ ::
+ */
+ private function GIFReadDescriptor()
+ {
+ $GIF_screen = array();
+
+ $this->GIFGetByte(9);
+ $GIF_screen = $this->GIF_buffer;
+ $GIF_colorF = $this->GIF_buffer[8] & 0x80 ? 1 : 0;
+ if ($GIF_colorF) {
+ $GIF_code = $this->GIF_buffer[8] & 0x07;
+ $GIF_sort = $this->GIF_buffer[8] & 0x20 ? 1 : 0;
+ } else {
+ $GIF_code = $this->GIF_colorC;
+ $GIF_sort = $this->GIF_sorted;
+ }
+ $GIF_size = 2 << $GIF_code;
+ $this->GIF_screen[4] &= 0x70;
+ $this->GIF_screen[4] |= 0x80;
+ $this->GIF_screen[4] |= $GIF_code;
+ if ($GIF_sort) {
+ $this->GIF_screen[4] |= 0x08;
+ }
+ $this->GIF_string = "GIF87a";
+ $this->GIFPutByte($this->GIF_screen);
+ if (1 == $GIF_colorF) {
+ $this->GIFGetByte(3 * $GIF_size);
+ $this->GIFPutByte($this->GIF_buffer);
+ } else {
+ $this->GIFPutByte($this->GIF_global);
+ }
+ $this->GIF_string .= chr(0x2C);
+ $GIF_screen[8] &= 0x40;
+ $this->GIFPutByte($GIF_screen);
+ $this->GIFGetByte(1);
+ $this->GIFPutByte($this->GIF_buffer);
+ for (;;) {
+ $this->GIFGetByte(1);
+ $this->GIFPutByte($this->GIF_buffer);
+ if (($u = $this->GIF_buffer[0]) == 0x00) {
+ break;
+ }
+ $this->GIFGetByte($u);
+ $this->GIFPutByte($this->GIF_buffer);
+ }
+ $this->GIF_string .= chr(0x3B);
+ /*
+ Add frames into $GIF_stream array...
+ */
+ $this->GIF_arrays[] = $this->GIF_string;
+ }
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFGetByte ( $len )
+ ::
+ */
+
+ /*
+ *
+ * 05.06.2007.
+ * Made a little modification
+ *
+ *
+ - function GIFGetByte ( $len ) {
+ - $this->GIF_buffer = Array ( );
+ -
+ - for ( $i = 0; $i < $len; $i++ ) {
+ + if ( $this->GIF_bfseek > strlen ( $this->GIF_stream ) ) {
+ + return 0;
+ + }
+ - $this->GIF_buffer [ ] = ord ( $this->GIF_stream { $this->GIF_bfseek++ } );
+ - }
+ + return 1;
+ - }
+ */
+ private function GIFGetByte($len)
+ {
+ $this->GIF_buffer = array();
+
+ for ($i = 0; $i < $len; $i++) {
+ if ($this->GIF_bfseek > strlen($this->GIF_stream)) {
+ return 0;
+ }
+ $this->GIF_buffer[] = ord($this->GIF_stream{$this->GIF_bfseek++});
+ }
+ return 1;
+ }
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFPutByte ( $bytes )
+ ::
+ */
+ private function GIFPutByte($bytes)
+ {
+ for ($i = 0; $i < count($bytes); $i++) {
+ $this->GIF_string .= chr($bytes[$i]);
+ }
+ }
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: PUBLIC FUNCTIONS
+ ::
+ ::
+ :: GIFGetFrames ( )
+ ::
+ */
+ public function GIFGetFrames()
+ {
+ return ($this->GIF_arrays);
+ }
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFGetDelays ( )
+ ::
+ */
+ public function GIFGetDelays()
+ {
+ return ($this->GIF_delays);
+ }
+}
diff --git a/Framework/Library/Think/Image/Driver/Gd.class.php b/Framework/Library/Think/Image/Driver/Gd.class.php
new file mode 100644
index 00000000..2186f8f2
--- /dev/null
+++ b/Framework/Library/Think/Image/Driver/Gd.class.php
@@ -0,0 +1,602 @@
+
+// +----------------------------------------------------------------------
+// | ImageGd.class.php 2013-03-05
+// +----------------------------------------------------------------------
+namespace Think\Image\Driver;
+
+use Think\Image;
+
+class Gd
+{
+ /**
+ * 图像资源对象
+ * @var resource
+ */
+ private $img;
+
+ /**
+ * 图像信息,包括width,height,type,mime,size
+ * @var array
+ */
+ private $info;
+
+ /**
+ * 构造方法,可用于打开一张图像
+ * @param string $imgname 图像路径
+ */
+ public function __construct($imgname = null)
+ {
+ $imgname && $this->open($imgname);
+ }
+
+ /**
+ * 打开一张图像
+ * @param string $imgname 图像路径
+ */
+ public function open($imgname)
+ {
+ //检测图像文件
+ //当本地文件时才判断如下if语句,否则如果是http外网图片时不判断
+ if (substr($imgname, 0, 4) != 'http' && !is_file($imgname)) {
+ E('不存在的图像文件');
+ }
+
+ //获取图像信息
+ $info = getimagesize($imgname);
+
+ //检测图像合法性
+ if (false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))) {
+ E('非法图像文件');
+ }
+
+ //设置图像信息
+ $this->info = array(
+ 'width' => $info[0],
+ 'height' => $info[1],
+ 'type' => image_type_to_extension($info[2], false),
+ 'mime' => $info['mime'],
+ );
+
+ //销毁已存在的图像
+ empty($this->img) || imagedestroy($this->img);
+
+ //打开图像
+ if ('gif' == $this->info['type']) {
+ $class = 'Think\\Image\\Driver\\GIF';
+ $this->gif = new $class($imgname);
+ $this->img = imagecreatefromstring($this->gif->image());
+ } else {
+ $fun = "imagecreatefrom{$this->info['type']}";
+ $this->img = $fun($imgname);
+ }
+ }
+
+ /**
+ * 保存图像
+ * @param string $imgname 图像保存名称
+ * @param string $type 图像类型
+ * @param integer $quality 图像质量
+ * @param boolean $interlace 是否对JPEG类型图像设置隔行扫描
+ */
+ public function save($imgname, $type = null, $quality = 80, $interlace = true)
+ {
+ if (empty($this->img)) {
+ E('没有可以被保存的图像资源');
+ }
+
+ //自动获取图像类型
+ if (is_null($type)) {
+ $type = $this->info['type'];
+ } else {
+ $type = strtolower($type);
+ }
+ //保存图像
+ if ('jpeg' == $type || 'jpg' == $type) {
+ //JPEG图像设置隔行扫描
+ imageinterlace($this->img, $interlace);
+ imagejpeg($this->img, $imgname, $quality);
+ } elseif ('gif' == $type && !empty($this->gif)) {
+ $this->gif->save($imgname);
+ } elseif ('png' == $type) {
+ //设定保存完整的 alpha 通道信息
+ imagesavealpha($this->img, true);
+ //ImagePNG生成图像的质量范围从0到9的
+ imagepng($this->img, $imgname, $quality / 10);
+ } else {
+ $fun = 'image' . $type;
+ $fun($this->img, $imgname);
+ }
+ }
+
+ /**
+ * 返回图像宽度
+ * @return integer 图像宽度
+ */
+ public function width()
+ {
+ if (empty($this->img)) {
+ E('没有指定图像资源');
+ }
+
+ return $this->info['width'];
+ }
+
+ /**
+ * 返回图像高度
+ * @return integer 图像高度
+ */
+ public function height()
+ {
+ if (empty($this->img)) {
+ E('没有指定图像资源');
+ }
+
+ return $this->info['height'];
+ }
+
+ /**
+ * 返回图像类型
+ * @return string 图像类型
+ */
+ public function type()
+ {
+ if (empty($this->img)) {
+ E('没有指定图像资源');
+ }
+
+ return $this->info['type'];
+ }
+
+ /**
+ * 返回图像MIME类型
+ * @return string 图像MIME类型
+ */
+ public function mime()
+ {
+ if (empty($this->img)) {
+ E('没有指定图像资源');
+ }
+
+ return $this->info['mime'];
+ }
+
+ /**
+ * 返回图像尺寸数组 0 - 图像宽度,1 - 图像高度
+ * @return array 图像尺寸
+ */
+ public function size()
+ {
+ if (empty($this->img)) {
+ E('没有指定图像资源');
+ }
+
+ return array($this->info['width'], $this->info['height']);
+ }
+
+ /**
+ * 裁剪图像
+ * @param integer $w 裁剪区域宽度
+ * @param integer $h 裁剪区域高度
+ * @param integer $x 裁剪区域x坐标
+ * @param integer $y 裁剪区域y坐标
+ * @param integer $width 图像保存宽度
+ * @param integer $height 图像保存高度
+ */
+ public function crop($w, $h, $x = 0, $y = 0, $width = null, $height = null)
+ {
+ if (empty($this->img)) {
+ E('没有可以被裁剪的图像资源');
+ }
+
+ //设置保存尺寸
+ empty($width) && $width = $w;
+ empty($height) && $height = $h;
+
+ do {
+ //创建新图像
+ $img = imagecreatetruecolor($width, $height);
+ // 调整默认颜色
+ $color = imagecolorallocate($img, 255, 255, 255);
+ imagefill($img, 0, 0, $color);
+ //取消默认的混色模式(优化原来生成的png图片为非透明的BUG)
+ if ('png' == $this->info['type']) {
+ imagealphablending($img, false);
+ }
+ //裁剪
+ imagecopyresampled($img, $this->img, 0, 0, $x, $y, $width, $height, $w, $h);
+ imagedestroy($this->img); //销毁原图
+
+ //设置新图像
+ $this->img = $img;
+ } while (!empty($this->gif) && $this->gifNext());
+
+ $this->info['width'] = $width;
+ $this->info['height'] = $height;
+ }
+
+ /**
+ * 生成缩略图
+ * @param integer $width 缩略图最大宽度
+ * @param integer $height 缩略图最大高度
+ * @param integer $type 缩略图裁剪类型
+ */
+ public function thumb($width, $height, $type = Image::IMAGE_THUMB_SCALE)
+ {
+ if (empty($this->img)) {
+ E('没有可以被缩略的图像资源');
+ }
+
+ //原图宽度和高度
+ $w = $this->info['width'];
+ $h = $this->info['height'];
+
+ /* 计算缩略图生成的必要参数 */
+ switch ($type) {
+ /* 等比例缩放 */
+ case Image::IMAGE_THUMB_SCALE:
+ //原图尺寸小于缩略图尺寸则不进行缩略
+ if ($w < $width && $h < $height) {
+ return;
+ }
+
+ //计算缩放比例
+ $scale = min($width / $w, $height / $h);
+
+ //设置缩略图的坐标及宽度和高度
+ $x = $y = 0;
+ $width = $w * $scale;
+ $height = $h * $scale;
+ break;
+
+ /* 居中裁剪 */
+ case Image::IMAGE_THUMB_CENTER:
+ //计算缩放比例
+ $scale = max($width / $w, $height / $h);
+
+ //设置缩略图的坐标及宽度和高度
+ $w = $width / $scale;
+ $h = $height / $scale;
+ $x = ($this->info['width'] - $w) / 2;
+ $y = ($this->info['height'] - $h) / 2;
+ break;
+
+ /* 左上角裁剪 */
+ case Image::IMAGE_THUMB_NORTHWEST:
+ //计算缩放比例
+ $scale = max($width / $w, $height / $h);
+
+ //设置缩略图的坐标及宽度和高度
+ $x = $y = 0;
+ $w = $width / $scale;
+ $h = $height / $scale;
+ break;
+
+ /* 右下角裁剪 */
+ case Image::IMAGE_THUMB_SOUTHEAST:
+ //计算缩放比例
+ $scale = max($width / $w, $height / $h);
+
+ //设置缩略图的坐标及宽度和高度
+ $w = $width / $scale;
+ $h = $height / $scale;
+ $x = $this->info['width'] - $w;
+ $y = $this->info['height'] - $h;
+ break;
+
+ /* 填充 */
+ case Image::IMAGE_THUMB_FILLED:
+ //计算缩放比例
+ if ($w < $width && $h < $height) {
+ $scale = 1;
+ } else {
+ $scale = min($width / $w, $height / $h);
+ }
+
+ //设置缩略图的坐标及宽度和高度
+ $neww = $w * $scale;
+ $newh = $h * $scale;
+ $posx = ($width - $w * $scale) / 2;
+ $posy = ($height - $h * $scale) / 2;
+
+ do {
+ //创建新图像
+ $img = imagecreatetruecolor($width, $height);
+ // 调整默认颜色
+ $color = imagecolorallocate($img, 255, 255, 255);
+ imagefill($img, 0, 0, $color);
+
+ //裁剪
+ imagecopyresampled($img, $this->img, $posx, $posy, $x, $y, $neww, $newh, $w, $h);
+ imagedestroy($this->img); //销毁原图
+ $this->img = $img;
+ } while (!empty($this->gif) && $this->gifNext());
+
+ $this->info['width'] = $width;
+ $this->info['height'] = $height;
+ return;
+
+ /* 固定 */
+ case Image::IMAGE_THUMB_FIXED:
+ $x = $y = 0;
+ break;
+
+ default:
+ E('不支持的缩略图裁剪类型');
+ }
+
+ /* 裁剪图像 */
+ $this->crop($w, $h, $x, $y, $width, $height);
+ }
+
+ /**
+ * 添加水印
+ * @param string $source 水印图片路径
+ * @param integer $locate 水印位置
+ * @param integer $alpha 水印透明度
+ */
+ public function water($source, $locate = Image::IMAGE_WATER_SOUTHEAST, $alpha = 80)
+ {
+ //资源检测
+ if (empty($this->img)) {
+ E('没有可以被添加水印的图像资源');
+ }
+
+ if (!is_file($source)) {
+ E('水印图像不存在');
+ }
+
+ //获取水印图像信息
+ $info = getimagesize($source);
+ if (false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))) {
+ E('非法水印文件');
+ }
+
+ //创建水印图像资源
+ $fun = 'imagecreatefrom' . image_type_to_extension($info[2], false);
+ $water = $fun($source);
+
+ //设定水印图像的混色模式
+ imagealphablending($water, true);
+
+ /* 设定水印位置 */
+ switch ($locate) {
+ /* 右下角水印 */
+ case Image::IMAGE_WATER_SOUTHEAST:
+ $x = $this->info['width'] - $info[0];
+ $y = $this->info['height'] - $info[1];
+ break;
+
+ /* 左下角水印 */
+ case Image::IMAGE_WATER_SOUTHWEST:
+ $x = 0;
+ $y = $this->info['height'] - $info[1];
+ break;
+
+ /* 左上角水印 */
+ case Image::IMAGE_WATER_NORTHWEST:
+ $x = $y = 0;
+ break;
+
+ /* 右上角水印 */
+ case Image::IMAGE_WATER_NORTHEAST:
+ $x = $this->info['width'] - $info[0];
+ $y = 0;
+ break;
+
+ /* 居中水印 */
+ case Image::IMAGE_WATER_CENTER:
+ $x = ($this->info['width'] - $info[0]) / 2;
+ $y = ($this->info['height'] - $info[1]) / 2;
+ break;
+
+ /* 下居中水印 */
+ case Image::IMAGE_WATER_SOUTH:
+ $x = ($this->info['width'] - $info[0]) / 2;
+ $y = $this->info['height'] - $info[1];
+ break;
+
+ /* 右居中水印 */
+ case Image::IMAGE_WATER_EAST:
+ $x = $this->info['width'] - $info[0];
+ $y = ($this->info['height'] - $info[1]) / 2;
+ break;
+
+ /* 上居中水印 */
+ case Image::IMAGE_WATER_NORTH:
+ $x = ($this->info['width'] - $info[0]) / 2;
+ $y = 0;
+ break;
+
+ /* 左居中水印 */
+ case Image::IMAGE_WATER_WEST:
+ $x = 0;
+ $y = ($this->info['height'] - $info[1]) / 2;
+ break;
+
+ default:
+ /* 自定义水印坐标 */
+ if (is_array($locate)) {
+ list($x, $y) = $locate;
+ } else {
+ E('不支持的水印位置类型');
+ }
+ }
+
+ do {
+ //添加水印
+ $src = imagecreatetruecolor($info[0], $info[1]);
+ // 调整默认颜色
+ $color = imagecolorallocate($src, 255, 255, 255);
+ imagefill($src, 0, 0, $color);
+
+ imagecopy($src, $this->img, 0, 0, $x, $y, $info[0], $info[1]);
+ imagecopy($src, $water, 0, 0, 0, 0, $info[0], $info[1]);
+ imagecopymerge($this->img, $src, $x, $y, 0, 0, $info[0], $info[1], $alpha);
+
+ //销毁零时图片资源
+ imagedestroy($src);
+ } while (!empty($this->gif) && $this->gifNext());
+
+ //销毁水印资源
+ imagedestroy($water);
+ }
+
+ /**
+ * 图像添加文字
+ * @param string $text 添加的文字
+ * @param string $font 字体路径
+ * @param integer $size 字号
+ * @param string $color 文字颜色
+ * @param integer $locate 文字写入位置
+ * @param integer $offset 文字相对当前位置的偏移量
+ * @param integer $angle 文字倾斜角度
+ */
+ public function text($text, $font, $size, $color = '#00000000',
+ $locate = Image::IMAGE_WATER_SOUTHEAST, $offset = 0, $angle = 0) {
+ //资源检测
+ if (empty($this->img)) {
+ E('没有可以被写入文字的图像资源');
+ }
+
+ if (!is_file($font)) {
+ E("不存在的字体文件:{$font}");
+ }
+
+ //获取文字信息
+ $info = imagettfbbox($size, $angle, $font, $text);
+ $minx = min($info[0], $info[2], $info[4], $info[6]);
+ $maxx = max($info[0], $info[2], $info[4], $info[6]);
+ $miny = min($info[1], $info[3], $info[5], $info[7]);
+ $maxy = max($info[1], $info[3], $info[5], $info[7]);
+
+ /* 计算文字初始坐标和尺寸 */
+ $x = $minx;
+ $y = abs($miny);
+ $w = $maxx - $minx;
+ $h = $maxy - $miny;
+
+ /* 设定文字位置 */
+ switch ($locate) {
+ /* 右下角文字 */
+ case Image::IMAGE_WATER_SOUTHEAST:
+ $x += $this->info['width'] - $w;
+ $y += $this->info['height'] - $h;
+ break;
+
+ /* 左下角文字 */
+ case Image::IMAGE_WATER_SOUTHWEST:
+ $y += $this->info['height'] - $h;
+ break;
+
+ /* 左上角文字 */
+ case Image::IMAGE_WATER_NORTHWEST:
+ // 起始坐标即为左上角坐标,无需调整
+ break;
+
+ /* 右上角文字 */
+ case Image::IMAGE_WATER_NORTHEAST:
+ $x += $this->info['width'] - $w;
+ break;
+
+ /* 居中文字 */
+ case Image::IMAGE_WATER_CENTER:
+ $x += ($this->info['width'] - $w) / 2;
+ $y += ($this->info['height'] - $h) / 2;
+ break;
+
+ /* 下居中文字 */
+ case Image::IMAGE_WATER_SOUTH:
+ $x += ($this->info['width'] - $w) / 2;
+ $y += $this->info['height'] - $h;
+ break;
+
+ /* 右居中文字 */
+ case Image::IMAGE_WATER_EAST:
+ $x += $this->info['width'] - $w;
+ $y += ($this->info['height'] - $h) / 2;
+ break;
+
+ /* 上居中文字 */
+ case Image::IMAGE_WATER_NORTH:
+ $x += ($this->info['width'] - $w) / 2;
+ break;
+
+ /* 左居中文字 */
+ case Image::IMAGE_WATER_WEST:
+ $y += ($this->info['height'] - $h) / 2;
+ break;
+
+ default:
+ /* 自定义文字坐标 */
+ if (is_array($locate)) {
+ list($posx, $posy) = $locate;
+ $x += $posx;
+ $y += $posy;
+ } else {
+ E('不支持的文字位置类型');
+ }
+ }
+
+ /* 设置偏移量 */
+ if (is_array($offset)) {
+ $offset = array_map('intval', $offset);
+ list($ox, $oy) = $offset;
+ } else {
+ $offset = intval($offset);
+ $ox = $oy = $offset;
+ }
+
+ /* 设置颜色 */
+ if (is_string($color) && 0 === strpos($color, '#')) {
+ $color = str_split(substr($color, 1), 2);
+ $color = array_map('hexdec', $color);
+ if (empty($color[3]) || $color[3] > 127) {
+ $color[3] = 0;
+ }
+ } elseif (!is_array($color)) {
+ E('错误的颜色值');
+ }
+
+ do {
+ /* 写入文字 */
+ $col = imagecolorallocatealpha($this->img, $color[0], $color[1], $color[2], $color[3]);
+ imagettftext($this->img, $size, $angle, $x + $ox, $y + $oy, $col, $font, $text);
+ } while (!empty($this->gif) && $this->gifNext());
+ }
+
+ /* 切换到GIF的下一帧并保存当前帧,内部使用 */
+ private function gifNext()
+ {
+ ob_start();
+ ob_implicit_flush(0);
+ imagegif($this->img);
+ $img = ob_get_clean();
+
+ $this->gif->image($img);
+ $next = $this->gif->nextImage();
+
+ if ($next) {
+ $this->img = imagecreatefromstring($next);
+ return $next;
+ } else {
+ $this->img = imagecreatefromstring($this->gif->image());
+ return false;
+ }
+ }
+
+ /**
+ * 析构方法,用于销毁图像资源
+ */
+ public function __destruct()
+ {
+ empty($this->img) || imagedestroy($this->img);
+ }
+}
diff --git a/Framework/Library/Think/Image/Driver/Imagick.class.php b/Framework/Library/Think/Image/Driver/Imagick.class.php
new file mode 100644
index 00000000..20c6a127
--- /dev/null
+++ b/Framework/Library/Think/Image/Driver/Imagick.class.php
@@ -0,0 +1,643 @@
+
+// +----------------------------------------------------------------------
+// | ImageImagick.class.php 2013-03-06
+// +----------------------------------------------------------------------
+namespace Think\Image\Driver;
+
+use Think\Image;
+
+class Imagick
+{
+ /**
+ * 图像资源对象
+ * @var resource
+ */
+ private $img;
+
+ /**
+ * 图像信息,包括width,height,type,mime,size
+ * @var array
+ */
+ private $info;
+
+ /**
+ * 构造方法,可用于打开一张图像
+ * @param string $imgname 图像路径
+ */
+ public function __construct($imgname = null)
+ {
+ $imgname && $this->open($imgname);
+ }
+
+ /**
+ * 打开一张图像
+ * @param string $imgname 图像路径
+ */
+ public function open($imgname)
+ {
+ //检测图像文件
+ if (!is_file($imgname)) {
+ E('不存在的图像文件');
+ }
+
+ //销毁已存在的图像
+ empty($this->img) || $this->img->destroy();
+
+ //载入图像
+ $this->img = new \Imagick(realpath($imgname));
+
+ //设置图像信息
+ $this->info = array(
+ 'width' => $this->img->getImageWidth(),
+ 'height' => $this->img->getImageHeight(),
+ 'type' => strtolower($this->img->getImageFormat()),
+ 'mime' => $this->img->getImageMimeType(),
+ );
+ }
+
+ /**
+ * 保存图像
+ * @param string $imgname 图像保存名称
+ * @param string $type 图像类型
+ * @param integer $quality JPEG图像质量
+ * @param boolean $interlace 是否对JPEG类型图像设置隔行扫描
+ */
+ public function save($imgname, $type = null, $quality = 80, $interlace = true)
+ {
+ if (empty($this->img)) {
+ E('没有可以被保存的图像资源');
+ }
+
+ //设置图片类型
+ if (is_null($type)) {
+ $type = $this->info['type'];
+ } else {
+ $type = strtolower($type);
+ $this->img->setImageFormat($type);
+ }
+
+ //JPEG图像设置隔行扫描
+ if ('jpeg' == $type || 'jpg' == $type) {
+ $this->img->setImageInterlaceScheme(1);
+ }
+
+ // 设置图像质量
+ $this->img->setImageCompressionQuality($quality);
+
+ //去除图像配置信息
+ $this->img->stripImage();
+
+ //保存图像
+ $imgname = realpath(dirname($imgname)) . '/' . basename($imgname); //强制绝对路径
+ if ('gif' == $type) {
+ $this->img->writeImages($imgname, true);
+ } else {
+ $this->img->writeImage($imgname);
+ }
+ }
+
+ /**
+ * 返回图像宽度
+ * @return integer 图像宽度
+ */
+ public function width()
+ {
+ if (empty($this->img)) {
+ E('没有指定图像资源');
+ }
+
+ return $this->info['width'];
+ }
+
+ /**
+ * 返回图像高度
+ * @return integer 图像高度
+ */
+ public function height()
+ {
+ if (empty($this->img)) {
+ E('没有指定图像资源');
+ }
+
+ return $this->info['height'];
+ }
+
+ /**
+ * 返回图像类型
+ * @return string 图像类型
+ */
+ public function type()
+ {
+ if (empty($this->img)) {
+ E('没有指定图像资源');
+ }
+
+ return $this->info['type'];
+ }
+
+ /**
+ * 返回图像MIME类型
+ * @return string 图像MIME类型
+ */
+ public function mime()
+ {
+ if (empty($this->img)) {
+ E('没有指定图像资源');
+ }
+
+ return $this->info['mime'];
+ }
+
+ /**
+ * 返回图像尺寸数组 0 - 图像宽度,1 - 图像高度
+ * @return array 图像尺寸
+ */
+ public function size()
+ {
+ if (empty($this->img)) {
+ E('没有指定图像资源');
+ }
+
+ return array($this->info['width'], $this->info['height']);
+ }
+
+ /**
+ * 裁剪图像
+ * @param integer $w 裁剪区域宽度
+ * @param integer $h 裁剪区域高度
+ * @param integer $x 裁剪区域x坐标
+ * @param integer $y 裁剪区域y坐标
+ * @param integer $width 图像保存宽度
+ * @param integer $height 图像保存高度
+ */
+ public function crop($w, $h, $x = 0, $y = 0, $width = null, $height = null)
+ {
+ if (empty($this->img)) {
+ E('没有可以被裁剪的图像资源');
+ }
+
+ //设置保存尺寸
+ empty($width) && $width = $w;
+ empty($height) && $height = $h;
+
+ //裁剪图片
+ if ('gif' == $this->info['type']) {
+ $img = $this->img->coalesceImages();
+ $this->img->destroy(); //销毁原图
+
+ //循环裁剪每一帧
+ do {
+ $this->_crop($w, $h, $x, $y, $width, $height, $img);
+ } while ($img->nextImage());
+
+ //压缩图片
+ $this->img = $img->deconstructImages();
+ $img->destroy(); //销毁零时图片
+ } else {
+ $this->_crop($w, $h, $x, $y, $width, $height);
+ }
+ }
+
+ /* 裁剪图片,内部调用 */
+ private function _crop($w, $h, $x, $y, $width, $height, $img = null)
+ {
+ is_null($img) && $img = $this->img;
+
+ //裁剪
+ $info = $this->info;
+ if (0 != $x || 0 != $y || $w != $info['width'] || $h != $info['height']) {
+ $img->cropImage($w, $h, $x, $y);
+ $img->setImagePage($w, $h, 0, 0); //调整画布和图片一致
+ }
+
+ //调整大小
+ if ($w != $width || $h != $height) {
+ $img->sampleImage($width, $height);
+ }
+
+ //设置缓存尺寸
+ $this->info['width'] = $width;
+ $this->info['height'] = $height;
+ }
+
+ /**
+ * 生成缩略图
+ * @param integer $width 缩略图最大宽度
+ * @param integer $height 缩略图最大高度
+ * @param integer $type 缩略图裁剪类型
+ */
+ public function thumb($width, $height, $type = Image::IMAGE_THUMB_SCALE)
+ {
+ if (empty($this->img)) {
+ E('没有可以被缩略的图像资源');
+ }
+
+ //原图宽度和高度
+ $w = $this->info['width'];
+ $h = $this->info['height'];
+
+ /* 计算缩略图生成的必要参数 */
+ switch ($type) {
+ /* 等比例缩放 */
+ case Image::IMAGE_THUMB_SCALE:
+ //原图尺寸小于缩略图尺寸则不进行缩略
+ if ($w < $width && $h < $height) {
+ return;
+ }
+
+ //计算缩放比例
+ $scale = min($width / $w, $height / $h);
+
+ //设置缩略图的坐标及宽度和高度
+ $x = $y = 0;
+ $width = $w * $scale;
+ $height = $h * $scale;
+ break;
+
+ /* 居中裁剪 */
+ case Image::IMAGE_THUMB_CENTER:
+ //计算缩放比例
+ $scale = max($width / $w, $height / $h);
+
+ //设置缩略图的坐标及宽度和高度
+ $w = $width / $scale;
+ $h = $height / $scale;
+ $x = ($this->info['width'] - $w) / 2;
+ $y = ($this->info['height'] - $h) / 2;
+ break;
+
+ /* 左上角裁剪 */
+ case Image::IMAGE_THUMB_NORTHWEST:
+ //计算缩放比例
+ $scale = max($width / $w, $height / $h);
+
+ //设置缩略图的坐标及宽度和高度
+ $x = $y = 0;
+ $w = $width / $scale;
+ $h = $height / $scale;
+ break;
+
+ /* 右下角裁剪 */
+ case Image::IMAGE_THUMB_SOUTHEAST:
+ //计算缩放比例
+ $scale = max($width / $w, $height / $h);
+
+ //设置缩略图的坐标及宽度和高度
+ $w = $width / $scale;
+ $h = $height / $scale;
+ $x = $this->info['width'] - $w;
+ $y = $this->info['height'] - $h;
+ break;
+
+ /* 填充 */
+ case Image::IMAGE_THUMB_FILLED:
+ //计算缩放比例
+ if ($w < $width && $h < $height) {
+ $scale = 1;
+ } else {
+ $scale = min($width / $w, $height / $h);
+ }
+
+ //设置缩略图的坐标及宽度和高度
+ $neww = $w * $scale;
+ $newh = $h * $scale;
+ $posx = ($width - $w * $scale) / 2;
+ $posy = ($height - $h * $scale) / 2;
+
+ //创建一张新图像
+ $newimg = new \Imagick();
+ $newimg->newImage($width, $height, 'white', $this->info['type']);
+
+ if ('gif' == $this->info['type']) {
+ $imgs = $this->img->coalesceImages();
+ $img = new \Imagick();
+ $this->img->destroy(); //销毁原图
+
+ //循环填充每一帧
+ do {
+ //填充图像
+ $image = $this->_fill($newimg, $posx, $posy, $neww, $newh, $imgs);
+
+ $img->addImage($image);
+ $img->setImageDelay($imgs->getImageDelay());
+ $img->setImagePage($width, $height, 0, 0);
+
+ $image->destroy(); //销毁零时图片
+
+ } while ($imgs->nextImage());
+
+ //压缩图片
+ $this->img->destroy();
+ $this->img = $img->deconstructImages();
+ $imgs->destroy(); //销毁零时图片
+ $img->destroy(); //销毁零时图片
+
+ } else {
+ //填充图像
+ $img = $this->_fill($newimg, $posx, $posy, $neww, $newh);
+ //销毁原图
+ $this->img->destroy();
+ $this->img = $img;
+ }
+
+ //设置新图像属性
+ $this->info['width'] = $width;
+ $this->info['height'] = $height;
+ return;
+
+ /* 固定 */
+ case Image::IMAGE_THUMB_FIXED:
+ $x = $y = 0;
+ break;
+
+ default:
+ E('不支持的缩略图裁剪类型');
+ }
+
+ /* 裁剪图像 */
+ $this->crop($w, $h, $x, $y, $width, $height);
+ }
+
+ /* 填充指定图像,内部使用 */
+ private function _fill($newimg, $posx, $posy, $neww, $newh, $img = null)
+ {
+ is_null($img) && $img = $this->img;
+
+ /* 将指定图片绘入空白图片 */
+ $draw = new \ImagickDraw();
+ $draw->composite($img->getImageCompose(), $posx, $posy, $neww, $newh, $img);
+ $image = $newimg->clone();
+ $image->drawImage($draw);
+ $draw->destroy();
+
+ return $image;
+ }
+
+ /**
+ * 添加水印
+ * @param string $source 水印图片路径
+ * @param integer $locate 水印位置
+ * @param integer $alpha 水印透明度
+ */
+ public function water($source, $locate = Image::IMAGE_WATER_SOUTHEAST, $alpha = 80)
+ {
+ //资源检测
+ if (empty($this->img)) {
+ E('没有可以被添加水印的图像资源');
+ }
+
+ if (!is_file($source)) {
+ E('水印图像不存在');
+ }
+
+ //创建水印图像资源
+ $water = new \Imagick(realpath($source));
+ $info = array($water->getImageWidth(), $water->getImageHeight());
+
+ /* 设定水印位置 */
+ switch ($locate) {
+ /* 右下角水印 */
+ case Image::IMAGE_WATER_SOUTHEAST:
+ $x = $this->info['width'] - $info[0];
+ $y = $this->info['height'] - $info[1];
+ break;
+
+ /* 左下角水印 */
+ case Image::IMAGE_WATER_SOUTHWEST:
+ $x = 0;
+ $y = $this->info['height'] - $info[1];
+ break;
+
+ /* 左上角水印 */
+ case Image::IMAGE_WATER_NORTHWEST:
+ $x = $y = 0;
+ break;
+
+ /* 右上角水印 */
+ case Image::IMAGE_WATER_NORTHEAST:
+ $x = $this->info['width'] - $info[0];
+ $y = 0;
+ break;
+
+ /* 居中水印 */
+ case Image::IMAGE_WATER_CENTER:
+ $x = ($this->info['width'] - $info[0]) / 2;
+ $y = ($this->info['height'] - $info[1]) / 2;
+ break;
+
+ /* 下居中水印 */
+ case Image::IMAGE_WATER_SOUTH:
+ $x = ($this->info['width'] - $info[0]) / 2;
+ $y = $this->info['height'] - $info[1];
+ break;
+
+ /* 右居中水印 */
+ case Image::IMAGE_WATER_EAST:
+ $x = $this->info['width'] - $info[0];
+ $y = ($this->info['height'] - $info[1]) / 2;
+ break;
+
+ /* 上居中水印 */
+ case Image::IMAGE_WATER_NORTH:
+ $x = ($this->info['width'] - $info[0]) / 2;
+ $y = 0;
+ break;
+
+ /* 左居中水印 */
+ case Image::IMAGE_WATER_WEST:
+ $x = 0;
+ $y = ($this->info['height'] - $info[1]) / 2;
+ break;
+
+ default:
+ /* 自定义水印坐标 */
+ if (is_array($locate)) {
+ list($x, $y) = $locate;
+ } else {
+ E('不支持的水印位置类型');
+ }
+ }
+
+ //创建绘图资源
+ $draw = new \ImagickDraw();
+ $draw->composite($water->getImageCompose(), $x, $y, $info[0], $info[1], $water);
+
+ if ('gif' == $this->info['type']) {
+ $img = $this->img->coalesceImages();
+ $this->img->destroy(); //销毁原图
+
+ do {
+ //添加水印
+ $img->drawImage($draw);
+ } while ($img->nextImage());
+
+ //压缩图片
+ $this->img = $img->deconstructImages();
+ $img->destroy(); //销毁零时图片
+
+ } else {
+ //添加水印
+ $this->img->drawImage($draw);
+ }
+
+ //销毁水印资源
+ $draw->destroy();
+ $water->destroy();
+ }
+
+ /**
+ * 图像添加文字
+ * @param string $text 添加的文字
+ * @param string $font 字体路径
+ * @param integer $size 字号
+ * @param string $color 文字颜色
+ * @param integer $locate 文字写入位置
+ * @param integer $offset 文字相对当前位置的偏移量
+ * @param integer $angle 文字倾斜角度
+ */
+ public function text($text, $font, $size, $color = '#00000000',
+ $locate = Image::IMAGE_WATER_SOUTHEAST, $offset = 0, $angle = 0) {
+ //资源检测
+ if (empty($this->img)) {
+ E('没有可以被写入文字的图像资源');
+ }
+
+ if (!is_file($font)) {
+ E("不存在的字体文件:{$font}");
+ }
+
+ //获取颜色和透明度
+ if (is_array($color)) {
+ $color = array_map('dechex', $color);
+ foreach ($color as &$value) {
+ $value = str_pad($value, 2, '0', STR_PAD_LEFT);
+ }
+ $color = '#' . implode('', $color);
+ } elseif (!is_string($color) || 0 !== strpos($color, '#')) {
+ E('错误的颜色值');
+ }
+ $col = substr($color, 0, 7);
+ $alp = strlen($color) == 9 ? substr($color, -2) : 0;
+
+ //获取文字信息
+ $draw = new \ImagickDraw();
+ $draw->setFont(realpath($font));
+ $draw->setFontSize($size);
+ $draw->setFillColor($col);
+ $draw->setFillAlpha(1 - hexdec($alp) / 127);
+ $draw->setTextAntialias(true);
+ $draw->setStrokeAntialias(true);
+
+ $metrics = $this->img->queryFontMetrics($draw, $text);
+
+ /* 计算文字初始坐标和尺寸 */
+ $x = 0;
+ $y = $metrics['ascender'];
+ $w = $metrics['textWidth'];
+ $h = $metrics['textHeight'];
+
+ /* 设定文字位置 */
+ switch ($locate) {
+ /* 右下角文字 */
+ case Image::IMAGE_WATER_SOUTHEAST:
+ $x += $this->info['width'] - $w;
+ $y += $this->info['height'] - $h;
+ break;
+
+ /* 左下角文字 */
+ case Image::IMAGE_WATER_SOUTHWEST:
+ $y += $this->info['height'] - $h;
+ break;
+
+ /* 左上角文字 */
+ case Image::IMAGE_WATER_NORTHWEST:
+ // 起始坐标即为左上角坐标,无需调整
+ break;
+
+ /* 右上角文字 */
+ case Image::IMAGE_WATER_NORTHEAST:
+ $x += $this->info['width'] - $w;
+ break;
+
+ /* 居中文字 */
+ case Image::IMAGE_WATER_CENTER:
+ $x += ($this->info['width'] - $w) / 2;
+ $y += ($this->info['height'] - $h) / 2;
+ break;
+
+ /* 下居中文字 */
+ case Image::IMAGE_WATER_SOUTH:
+ $x += ($this->info['width'] - $w) / 2;
+ $y += $this->info['height'] - $h;
+ break;
+
+ /* 右居中文字 */
+ case Image::IMAGE_WATER_EAST:
+ $x += $this->info['width'] - $w;
+ $y += ($this->info['height'] - $h) / 2;
+ break;
+
+ /* 上居中文字 */
+ case Image::IMAGE_WATER_NORTH:
+ $x += ($this->info['width'] - $w) / 2;
+ break;
+
+ /* 左居中文字 */
+ case Image::IMAGE_WATER_WEST:
+ $y += ($this->info['height'] - $h) / 2;
+ break;
+
+ default:
+ /* 自定义文字坐标 */
+ if (is_array($locate)) {
+ list($posx, $posy) = $locate;
+ $x += $posx;
+ $y += $posy;
+ } else {
+ E('不支持的文字位置类型');
+ }
+ }
+
+ /* 设置偏移量 */
+ if (is_array($offset)) {
+ $offset = array_map('intval', $offset);
+ list($ox, $oy) = $offset;
+ } else {
+ $offset = intval($offset);
+ $ox = $oy = $offset;
+ }
+
+ /* 写入文字 */
+ if ('gif' == $this->info['type']) {
+ $img = $this->img->coalesceImages();
+ $this->img->destroy(); //销毁原图
+ do {
+ $img->annotateImage($draw, $x + $ox, $y + $oy, $angle, $text);
+ } while ($img->nextImage());
+
+ //压缩图片
+ $this->img = $img->deconstructImages();
+ $img->destroy(); //销毁零时图片
+
+ } else {
+ $this->img->annotateImage($draw, $x + $ox, $y + $oy, $angle, $text);
+ }
+ $draw->destroy();
+ }
+
+ /**
+ * 析构方法,用于销毁图像资源
+ */
+ public function __destruct()
+ {
+ empty($this->img) || $this->img->destroy();
+ }
+}
diff --git a/Framework/Library/Think/Log.class.php b/Framework/Library/Think/Log.class.php
new file mode 100644
index 00000000..2bafe867
--- /dev/null
+++ b/Framework/Library/Think/Log.class.php
@@ -0,0 +1,112 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * 日志处理类
+ */
+class Log
+{
+
+ // 日志级别 从上到下,由低到高
+ const EMERG = 'EMERG'; // 严重错误: 导致系统崩溃无法使用
+ const ALERT = 'ALERT'; // 警戒性错误: 必须被立即修改的错误
+ const CRIT = 'CRIT'; // 临界值错误: 超过临界值的错误,例如一天24小时,而输入的是25小时这样
+ const ERR = 'ERR'; // 一般错误: 一般性错误
+ const WARN = 'WARN'; // 警告性错误: 需要发出警告的错误
+ const NOTICE = 'NOTIC'; // 通知: 程序可以运行但是还不够完美的错误
+ const INFO = 'INFO'; // 信息: 程序输出信息
+ const DEBUG = 'DEBUG'; // 调试: 调试信息
+ const SQL = 'SQL'; // SQL:SQL语句 注意只在调试模式开启时有效
+
+ // 日志信息
+ protected static $log = array();
+
+ // 日志存储
+ protected static $storage = null;
+
+ // 日志初始化
+ public static function init($config = array())
+ {
+ $type = isset($config['type']) ? $config['type'] : 'File';
+ $class = strpos($type, '\\') ? $type : 'Think\\Log\\Driver\\' . ucwords(strtolower($type));
+ unset($config['type']);
+ self::$storage = new $class($config);
+ }
+
+ /**
+ * 记录日志 并且会过滤未经设置的级别
+ * @static
+ * @access public
+ * @param string $message 日志信息
+ * @param string $level 日志级别
+ * @param boolean $record 是否强制记录
+ * @return void
+ */
+ public static function record($message, $level = self::ERR, $record = false)
+ {
+ if ($record || false !== strpos(C('LOG_LEVEL'), $level)) {
+ self::$log[] = "{$level}: {$message}\r\n";
+ }
+ }
+
+ /**
+ * 日志保存
+ * @static
+ * @access public
+ * @param integer $type 日志记录方式
+ * @param string $destination 写入目标
+ * @return void
+ */
+ public static function save($type = '', $destination = '')
+ {
+ if (empty(self::$log)) {
+ return;
+ }
+
+ if (empty($destination)) {
+ $destination = C('LOG_PATH') . date('y_m_d') . '.log';
+ }
+ if (!self::$storage) {
+ $type = $type ?: C('LOG_TYPE');
+ $class = 'Think\\Log\\Driver\\' . ucwords($type);
+ self::$storage = new $class();
+ }
+ $message = implode('', self::$log);
+ self::$storage->write($message, $destination);
+ // 保存后清空日志缓存
+ self::$log = array();
+ }
+
+ /**
+ * 日志直接写入
+ * @static
+ * @access public
+ * @param string $message 日志信息
+ * @param string $level 日志级别
+ * @param integer $type 日志记录方式
+ * @param string $destination 写入目标
+ * @return void
+ */
+ public static function write($message, $level = self::ERR, $type = '', $destination = '')
+ {
+ if (!self::$storage) {
+ $type = $type ?: C('LOG_TYPE');
+ $class = 'Think\\Log\\Driver\\' . ucwords($type);
+ $config['log_path'] = C('LOG_PATH');
+ self::$storage = new $class($config);
+ }
+ if (empty($destination)) {
+ $destination = C('LOG_PATH') . date('y_m_d') . '.log';
+ }
+ self::$storage->write("{$level}: {$message}", $destination);
+ }
+}
diff --git a/Framework/Library/Think/Log/Driver/File.class.php b/Framework/Library/Think/Log/Driver/File.class.php
new file mode 100644
index 00000000..447ed10b
--- /dev/null
+++ b/Framework/Library/Think/Log/Driver/File.class.php
@@ -0,0 +1,53 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think\Log\Driver;
+
+class File
+{
+
+ protected $config = array(
+ 'log_time_format' => ' c ',
+ 'log_file_size' => 2097152,
+ 'log_path' => '',
+ );
+
+ // 实例化并传入参数
+ public function __construct($config = array())
+ {
+ $this->config = array_merge($this->config, $config);
+ }
+
+ /**
+ * 日志写入接口
+ * @access public
+ * @param string $log 日志信息
+ * @param string $destination 写入目标
+ * @return void
+ */
+ public function write($log, $destination = '')
+ {
+ $now = date($this->config['log_time_format']);
+ if (empty($destination)) {
+ $destination = $this->config['log_path'] . date('y_m_d') . '.log';
+ }
+ // 自动创建日志目录
+ $log_dir = dirname($destination);
+ if (!is_dir($log_dir)) {
+ mkdir($log_dir, 0755, true);
+ }
+ //检测日志文件大小,超过配置大小则备份日志文件重新生成
+ if (is_file($destination) && floor($this->config['log_file_size']) <= filesize($destination)) {
+ rename($destination, dirname($destination) . '/' . time() . '-' . basename($destination));
+ }
+ error_log("[{$now}] " . $_SERVER['REMOTE_ADDR'] . ' ' . $_SERVER['REQUEST_URI'] . "\r\n{$log}\r\n", 3, $destination);
+ }
+}
diff --git a/Framework/Library/Think/Log/Driver/Sae.class.php b/Framework/Library/Think/Log/Driver/Sae.class.php
new file mode 100644
index 00000000..98b862cd
--- /dev/null
+++ b/Framework/Library/Think/Log/Driver/Sae.class.php
@@ -0,0 +1,52 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think\Log\Driver;
+
+class Sae
+{
+
+ protected $config = array(
+ 'log_time_format' => ' c ',
+ );
+
+ // 实例化并传入参数
+ public function __construct($config = array())
+ {
+ $this->config = array_merge($this->config, $config);
+ }
+
+ /**
+ * 日志写入接口
+ * @access public
+ * @param string $log 日志信息
+ * @param string $destination 写入目标
+ * @return void
+ */
+ public function write($log, $destination = '')
+ {
+ static $is_debug = null;
+ $now = date($this->config['log_time_format']);
+ $logstr = "[{$now}] " . $_SERVER['REMOTE_ADDR'] . ' ' . $_SERVER['REQUEST_URI'] . "\r\n{$log}\r\n";
+ if (is_null($is_debug)) {
+ preg_replace('@(\w+)\=([^;]*)@e', '$appSettings[\'\\1\']="\\2";', $_SERVER['HTTP_APPCOOKIE']);
+ $is_debug = in_array($_SERVER['HTTP_APPVERSION'], explode(',', $appSettings['debug'])) ? true : false;
+ }
+ if ($is_debug) {
+ sae_set_display_errors(false); //记录日志不将日志打印出来
+ }
+ sae_debug($logstr);
+ if ($is_debug) {
+ sae_set_display_errors(true);
+ }
+
+ }
+}
diff --git a/Framework/Library/Think/Model.class.php b/Framework/Library/Think/Model.class.php
new file mode 100644
index 00000000..a3e41865
--- /dev/null
+++ b/Framework/Library/Think/Model.class.php
@@ -0,0 +1,2124 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * ThinkPHP Model模型类
+ * 实现了ORM和ActiveRecords模式
+ */
+class Model
+{
+ // 操作状态
+ const MODEL_INSERT = 1; // 插入模型数据
+ const MODEL_UPDATE = 2; // 更新模型数据
+ const MODEL_BOTH = 3; // 包含上面两种方式
+ const MUST_VALIDATE = 1; // 必须验证
+ const EXISTS_VALIDATE = 0; // 表单存在字段则验证
+ const VALUE_VALIDATE = 2; // 表单值不为空则验证
+
+ // 当前数据库操作对象
+ protected $db = null;
+ // 数据库对象池
+ private $_db = array();
+ // 主键名称
+ protected $pk = 'id';
+ // 主键是否自动增长
+ protected $autoinc = false;
+ // 数据表前缀
+ protected $tablePrefix = null;
+ // 模型名称
+ protected $name = '';
+ // 数据库名称
+ protected $dbName = '';
+ //数据库配置
+ protected $connection = '';
+ // 数据表名(不包含表前缀)
+ protected $tableName = '';
+ // 实际数据表名(包含表前缀)
+ protected $trueTableName = '';
+ // 最近错误信息
+ protected $error = '';
+ // 字段信息
+ protected $fields = array();
+ // 数据信息
+ protected $data = array();
+ // 查询表达式参数
+ protected $options = array();
+ protected $_validate = array(); // 自动验证定义
+ protected $_auto = array(); // 自动完成定义
+ protected $_map = array(); // 字段映射定义
+ protected $_scope = array(); // 命名范围定义
+ // 是否自动检测数据表字段信息
+ protected $autoCheckFields = true;
+ // 是否批处理验证
+ protected $patchValidate = false;
+ // 链操作方法列表
+ protected $methods = array('strict', 'order', 'alias', 'having', 'group', 'lock', 'distinct', 'auto', 'filter', 'validate', 'result', 'token', 'index', 'force', 'master');
+
+ /**
+ * 架构函数
+ * 取得DB类的实例对象 字段检查
+ * @access public
+ * @param string $name 模型名称
+ * @param string $tablePrefix 表前缀
+ * @param mixed $connection 数据库连接信息
+ */
+ public function __construct($name = '', $tablePrefix = '', $connection = '')
+ {
+ // 模型初始化
+ $this->_initialize();
+ // 获取模型名称
+ if (!empty($name)) {
+ if (strpos($name, '.')) {
+ // 支持 数据库名.模型名的 定义
+ list($this->dbName, $this->name) = explode('.', $name);
+ } else {
+ $this->name = $name;
+ }
+ } elseif (empty($this->name)) {
+ $this->name = $this->getModelName();
+ }
+ // 设置表前缀
+ if (is_null($tablePrefix)) {
+ // 前缀为Null表示没有前缀
+ $this->tablePrefix = '';
+ } elseif ('' != $tablePrefix) {
+ $this->tablePrefix = $tablePrefix;
+ } elseif (!isset($this->tablePrefix)) {
+ $this->tablePrefix = !empty($this->connection) && !is_null(C($this->connection.'.DB_PREFIX')) ? C($this->connection.'.DB_PREFIX') : C('DB_PREFIX');
+ }
+
+ // 数据库初始化操作
+ // 获取数据库操作对象
+ // 当前模型有独立的数据库连接信息
+ $this->db(0, empty($this->connection) ? $connection : $this->connection, true);
+ }
+
+ /**
+ * 自动检测数据表信息
+ * @access protected
+ * @return void
+ */
+ protected function _checkTableInfo()
+ {
+ // 如果不是Model类 自动记录数据表信息
+ // 只在第一次执行记录
+ if (empty($this->fields)) {
+ // 如果数据表字段没有定义则自动获取
+ if (C('DB_FIELDS_CACHE')) {
+ $fields = F('_fields/' . strtolower($this->getTableName()));
+ if ($fields) {
+ $this->fields = $fields;
+ if (!empty($fields['_pk'])) {
+ $this->pk = $fields['_pk'];
+ }
+ return;
+ }
+ }
+ // 每次都会读取数据表信息
+ $this->flush();
+ }
+ }
+
+ /**
+ * 获取字段信息并缓存
+ * @access public
+ * @return void
+ */
+ public function flush()
+ {
+ // 缓存不存在则查询数据表信息
+ $this->db->setModel($this->name);
+ $tableName = $this->getTableName();
+ $fields = $this->db->getFields($tableName);
+ if (!$fields) {
+ // 无法获取字段信息
+ return false;
+ }
+ $this->fields = array_keys($fields);
+ unset($this->fields['_pk']);
+ foreach ($fields as $key => $val) {
+ // 记录字段类型
+ $type[$key] = $val['type'];
+ if ($val['primary']) {
+ // 增加复合主键支持
+ if (isset($this->fields['_pk']) && null != $this->fields['_pk']) {
+ if (is_string($this->fields['_pk'])) {
+ $this->pk = array($this->fields['_pk']);
+ $this->fields['_pk'] = $this->pk;
+ }
+ $this->pk[] = $key;
+ $this->fields['_pk'][] = $key;
+ } else {
+ $this->pk = $key;
+ $this->fields['_pk'] = $key;
+ }
+ if ($val['autoinc']) {
+ $this->autoinc = true;
+ }
+
+ }
+ }
+ // 记录字段类型信息
+ $this->fields['_type'] = $type;
+
+ // 2008-3-7 增加缓存开关控制
+ if (C('DB_FIELDS_CACHE')) {
+ // 永久缓存数据表信息
+ F('_fields/' . strtolower($tableName), $this->fields);
+ }
+ }
+
+ /**
+ * 设置数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @param mixed $value 值
+ * @return void
+ */
+ public function __set($name, $value)
+ {
+ // 设置数据对象属性
+ $this->data[$name] = $value;
+ }
+
+ /**
+ * 获取数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return isset($this->data[$name]) ? $this->data[$name] : null;
+ }
+
+ /**
+ * 检测数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @return boolean
+ */
+ public function __isset($name)
+ {
+ return isset($this->data[$name]);
+ }
+
+ /**
+ * 销毁数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @return void
+ */
+ public function __unset($name)
+ {
+ unset($this->data[$name]);
+ }
+
+ /**
+ * 利用__call方法实现一些特殊的Model方法
+ * @access public
+ * @param string $method 方法名称
+ * @param array $args 调用参数
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ if (in_array(strtolower($method), $this->methods, true)) {
+ // 连贯操作的实现
+ $this->options[strtolower($method)] = $args[0];
+ return $this;
+ } elseif (in_array(strtolower($method), array('count', 'sum', 'min', 'max', 'avg'), true)) {
+ // 统计查询的实现
+ $field = isset($args[0]) ? $args[0] : '*';
+ return $this->getField(strtoupper($method) . '(' . $field . ') AS tp_' . $method);
+ } elseif (strtolower(substr($method, 0, 5)) == 'getby') {
+ // 根据某个字段获取记录
+ $field = parse_name(substr($method, 5));
+ $where[$field] = $args[0];
+ return $this->where($where)->find();
+ } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
+ // 根据某个字段获取记录的某个值
+ $name = parse_name(substr($method, 10));
+ $where[$name] = $args[0];
+ return $this->where($where)->getField($args[1]);
+ } elseif (isset($this->_scope[$method])) {
+ // 命名范围的单独调用支持
+ return $this->scope($method, $args[0]);
+ } else {
+ E(__CLASS__ . ':' . $method . L('_METHOD_NOT_EXIST_'));
+ return;
+ }
+ }
+ // 回调方法 初始化模型
+ protected function _initialize()
+ {}
+
+ /**
+ * 对保存到数据库的数据进行处理
+ * @access protected
+ * @param mixed $data 要操作的数据
+ * @return boolean
+ */
+ protected function _facade($data)
+ {
+
+ // 检查数据字段合法性
+ if (!empty($this->fields)) {
+ if (!empty($this->options['field'])) {
+ $fields = $this->options['field'];
+ unset($this->options['field']);
+ if (is_string($fields)) {
+ $fields = explode(',', $fields);
+ }
+ } else {
+ $fields = $this->fields;
+ }
+ foreach ($data as $key => $val) {
+ if (!in_array($key, $fields, true)) {
+ if (!empty($this->options['strict'])) {
+ E(L('_DATA_TYPE_INVALID_') . ':[' . $key . '=>' . $val . ']');
+ }
+ unset($data[$key]);
+ } elseif (is_scalar($val)) {
+ // 字段类型检查 和 强制转换
+ $this->_parseType($data, $key);
+ }
+ }
+ }
+
+ // 安全过滤
+ if (!empty($this->options['filter'])) {
+ $data = array_map($this->options['filter'], $data);
+ unset($this->options['filter']);
+ }
+ $this->_before_write($data);
+ return $data;
+ }
+
+ // 写入数据前的回调方法 包括新增和更新
+ protected function _before_write(&$data)
+ {}
+
+ /**
+ * 新增数据
+ * @access public
+ * @param mixed $data 数据
+ * @param array $options 表达式
+ * @param boolean $replace 是否replace
+ * @return mixed
+ */
+ public function add($data = '', $options = array(), $replace = false)
+ {
+ if (empty($data)) {
+ // 没有传递数据,获取当前数据对象的值
+ if (!empty($this->data)) {
+ $data = $this->data;
+ // 重置数据
+ $this->data = array();
+ } else {
+ $this->error = L('_DATA_TYPE_INVALID_');
+ return false;
+ }
+ }
+ // 数据处理
+ $data = $this->_facade($data);
+ // 分析表达式
+ $options = $this->_parseOptions($options);
+ if (false === $this->_before_insert($data, $options)) {
+ return false;
+ }
+ // 写入数据到数据库
+ $result = $this->db->insert($data, $options, $replace);
+ if (false !== $result && is_numeric($result)) {
+ $pk = $this->getPk();
+ // 增加复合主键支持
+ if (is_array($pk)) {
+ return $result;
+ }
+
+ $insertId = $this->getLastInsID();
+ if ($insertId) {
+ // 自增主键返回插入ID
+ $data[$pk] = $insertId;
+ if (false === $this->_after_insert($data, $options)) {
+ return false;
+ }
+ return $insertId;
+ }
+ if (false === $this->_after_insert($data, $options)) {
+ return false;
+ }
+ }
+ return $result;
+ }
+ // 插入数据前的回调方法
+ protected function _before_insert(&$data, $options)
+ {}
+ // 插入成功后的回调方法
+ protected function _after_insert($data, $options)
+ {}
+
+ public function addAll($dataList, $options = array(), $replace = false)
+ {
+ if (empty($dataList)) {
+ $this->error = L('_DATA_TYPE_INVALID_');
+ return false;
+ }
+ // 数据处理
+ foreach ($dataList as $key => $data) {
+ $dataList[$key] = $this->_facade($data);
+ }
+ // 分析表达式
+ $options = $this->_parseOptions($options);
+ // 写入数据到数据库
+ $result = $this->db->insertAll($dataList, $options, $replace);
+ if (false !== $result) {
+ $insertId = $this->getLastInsID();
+ if ($insertId) {
+ return $insertId;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * 通过Select方式添加记录
+ * @access public
+ * @param string $fields 要插入的数据表字段名
+ * @param string $table 要插入的数据表名
+ * @param array $options 表达式
+ * @return boolean
+ */
+ public function selectAdd($fields = '', $table = '', $options = array())
+ {
+ // 分析表达式
+ $options = $this->_parseOptions($options);
+ // 写入数据到数据库
+ if (false === $result = $this->db->selectInsert($fields ?: $options['field'], $table ?: $this->getTableName(), $options)) {
+ // 数据库插入操作失败
+ $this->error = L('_OPERATION_WRONG_');
+ return false;
+ } else {
+ // 插入成功
+ return $result;
+ }
+ }
+
+ /**
+ * 保存数据
+ * @access public
+ * @param mixed $data 数据
+ * @param array $options 表达式
+ * @return boolean
+ */
+ public function save($data = '', $options = array())
+ {
+ if (empty($data)) {
+ // 没有传递数据,获取当前数据对象的值
+ if (!empty($this->data)) {
+ $data = $this->data;
+ // 重置数据
+ $this->data = array();
+ } else {
+ $this->error = L('_DATA_TYPE_INVALID_');
+ return false;
+ }
+ }
+ // 数据处理
+ $data = $this->_facade($data);
+ if (empty($data)) {
+ // 没有数据则不执行
+ $this->error = L('_DATA_TYPE_INVALID_');
+ return false;
+ }
+ // 分析表达式
+ $options = $this->_parseOptions($options);
+ $pk = $this->getPk();
+ if (!isset($options['where'])) {
+ // 如果存在主键数据 则自动作为更新条件
+ if (is_string($pk) && isset($data[$pk])) {
+ $where[$pk] = $data[$pk];
+ unset($data[$pk]);
+ } elseif (is_array($pk)) {
+ // 增加复合主键支持
+ foreach ($pk as $field) {
+ if (isset($data[$field])) {
+ $where[$field] = $data[$field];
+ } else {
+ // 如果缺少复合主键数据则不执行
+ $this->error = L('_OPERATION_WRONG_');
+ return false;
+ }
+ unset($data[$field]);
+ }
+ }
+ if (!isset($where)) {
+ // 如果没有任何更新条件则不执行
+ $this->error = L('_OPERATION_WRONG_');
+ return false;
+ } else {
+ $options['where'] = $where;
+ }
+ }
+
+ if (is_array($options['where']) && isset($options['where'][$pk])) {
+ $pkValue = $options['where'][$pk];
+ }
+ if (false === $this->_before_update($data, $options)) {
+ return false;
+ }
+ $result = $this->db->update($data, $options);
+ if (false !== $result && is_numeric($result)) {
+ if (isset($pkValue)) {
+ $data[$pk] = $pkValue;
+ }
+
+ $this->_after_update($data, $options);
+ }
+ return $result;
+ }
+ // 更新数据前的回调方法
+ protected function _before_update(&$data, $options)
+ {}
+ // 更新成功后的回调方法
+ protected function _after_update($data, $options)
+ {}
+
+ /**
+ * 删除数据
+ * @access public
+ * @param mixed $options 表达式
+ * @return mixed
+ */
+ public function delete($options = array())
+ {
+ $pk = $this->getPk();
+ if (empty($options) && empty($this->options['where'])) {
+ // 如果删除条件为空 则删除当前数据对象所对应的记录
+ if (!empty($this->data) && isset($this->data[$pk])) {
+ return $this->delete($this->data[$pk]);
+ } else {
+ return false;
+ }
+
+ }
+ if (is_numeric($options) || is_string($options)) {
+ // 根据主键删除记录
+ if (strpos($options, ',')) {
+ $where[$pk] = array('IN', $options);
+ } else {
+ $where[$pk] = $options;
+ }
+ $options = array();
+ $options['where'] = $where;
+ }
+ // 根据复合主键删除记录
+ if (is_array($options) && (count($options) > 0) && is_array($pk)) {
+ $count = 0;
+ foreach (array_keys($options) as $key) {
+ if (is_int($key)) {
+ $count++;
+ }
+
+ }
+ if (count($pk) == $count) {
+ $i = 0;
+ foreach ($pk as $field) {
+ $where[$field] = $options[$i];
+ unset($options[$i++]);
+ }
+ $options['where'] = $where;
+ } else {
+ return false;
+ }
+ }
+ // 分析表达式
+ $options = $this->_parseOptions($options);
+ if (empty($options['where'])) {
+ // 如果条件为空 不进行删除操作 除非设置 1=1
+ return false;
+ }
+ if (is_array($options['where']) && isset($options['where'][$pk])) {
+ $pkValue = $options['where'][$pk];
+ }
+
+ if (false === $this->_before_delete($options)) {
+ return false;
+ }
+ $result = $this->db->delete($options);
+ if (false !== $result && is_numeric($result)) {
+ $data = array();
+ if (isset($pkValue)) {
+ $data[$pk] = $pkValue;
+ }
+
+ $this->_after_delete($data, $options);
+ }
+ // 返回删除记录个数
+ return $result;
+ }
+ // 删除数据前的回调方法
+ protected function _before_delete($options)
+ {}
+ // 删除成功后的回调方法
+ protected function _after_delete($data, $options)
+ {}
+
+ /**
+ * 查询数据集
+ * @access public
+ * @param array $options 表达式参数
+ * @return mixed
+ */
+ public function select($options = array())
+ {
+ $pk = $this->getPk();
+ if (is_string($options) || is_numeric($options)) {
+ // 根据主键查询
+ if (strpos($options, ',')) {
+ $where[$pk] = array('IN', $options);
+ } else {
+ $where[$pk] = $options;
+ }
+ $options = array();
+ $options['where'] = $where;
+ } elseif (is_array($options) && (count($options) > 0) && is_array($pk)) {
+ // 根据复合主键查询
+ $count = 0;
+ foreach (array_keys($options) as $key) {
+ if (is_int($key)) {
+ $count++;
+ }
+
+ }
+ if (count($pk) == $count) {
+ $i = 0;
+ foreach ($pk as $field) {
+ $where[$field] = $options[$i];
+ unset($options[$i++]);
+ }
+ $options['where'] = $where;
+ } else {
+ return false;
+ }
+ } elseif (false === $options) {
+ // 用于子查询 不查询只返回SQL
+ $options['fetch_sql'] = true;
+ }
+ // 分析表达式
+ $options = $this->_parseOptions($options);
+ // 判断查询缓存
+ if (isset($options['cache'])) {
+ $cache = $options['cache'];
+ $key = is_string($cache['key']) ? $cache['key'] : md5(serialize($options));
+ $data = S($key, '', $cache);
+ if (false !== $data) {
+ return $data;
+ }
+ }
+ $resultSet = $this->db->select($options);
+ if (false === $resultSet) {
+ return false;
+ }
+ if (!empty($resultSet)) {
+ // 有查询结果
+ if (is_string($resultSet)) {
+ return $resultSet;
+ }
+
+ $resultSet = array_map(array($this, '_read_data'), $resultSet);
+ $this->_after_select($resultSet, $options);
+ if (isset($options['index'])) {
+ // 对数据集进行索引
+ $index = explode(',', $options['index']);
+ foreach ($resultSet as $result) {
+ $_key = $result[$index[0]];
+ if (isset($index[1]) && isset($result[$index[1]])) {
+ $cols[$_key] = $result[$index[1]];
+ } else {
+ $cols[$_key] = $result;
+ }
+ }
+ $resultSet = $cols;
+ }
+ }
+
+ if (isset($cache)) {
+ S($key, $resultSet, $cache);
+ }
+ return $resultSet;
+ }
+ // 查询成功后的回调方法
+ protected function _after_select(&$resultSet, $options)
+ {}
+
+ /**
+ * 生成查询SQL 可用于子查询
+ * @access public
+ * @return string
+ */
+ public function buildSql()
+ {
+ return '( ' . $this->fetchSql(true)->select() . ' )';
+ }
+
+ /**
+ * 分析表达式
+ * @access protected
+ * @param array $options 表达式参数
+ * @return array
+ */
+ protected function _parseOptions($options = array())
+ {
+ if (is_array($options)) {
+ $options = array_merge($this->options, $options);
+ }
+
+ if (!isset($options['table'])) {
+ // 自动获取表名
+ $options['table'] = $this->getTableName();
+ $fields = $this->fields;
+ } else {
+ // 指定数据表 则重新获取字段列表 但不支持类型检测
+ $fields = $this->getDbFields();
+ }
+
+ // 数据表别名
+ if (!empty($options['alias'])) {
+ $options['table'] .= ' ' . $options['alias'];
+ }
+ // 记录操作的模型名称
+ $options['model'] = $this->name;
+
+ // 字段类型验证
+ if (isset($options['where']) && is_array($options['where']) && !empty($fields) && !isset($options['join'])) {
+ // 对数组查询条件进行字段类型检查
+ foreach ($options['where'] as $key => $val) {
+ $key = trim($key);
+ if (in_array($key, $fields, true)) {
+ if (is_scalar($val)) {
+ $this->_parseType($options['where'], $key);
+ }
+ }
+ }
+ }
+ // 查询过后清空sql表达式组装 避免影响下次查询
+ $this->options = array();
+ // 表达式过滤
+ $this->_options_filter($options);
+ return $options;
+ }
+ // 表达式过滤回调方法
+ protected function _options_filter(&$options)
+ {}
+
+ /**
+ * 数据类型检测
+ * @access protected
+ * @param mixed $data 数据
+ * @param string $key 字段名
+ * @return void
+ */
+ protected function _parseType(&$data, $key)
+ {
+ if (!isset($this->options['bind'][':' . $key]) && isset($this->fields['_type'][$key])) {
+ $fieldType = strtolower($this->fields['_type'][$key]);
+ if (false !== strpos($fieldType, 'enum')) {
+ // 支持ENUM类型优先检测
+ } elseif (false === strpos($fieldType, 'bigint') && false !== strpos($fieldType, 'int')) {
+ $data[$key] = intval($data[$key]);
+ } elseif (false !== strpos($fieldType, 'float') || false !== strpos($fieldType, 'double')) {
+ $data[$key] = floatval($data[$key]);
+ } elseif (false !== strpos($fieldType, 'bool')) {
+ $data[$key] = (bool) $data[$key];
+ }
+ }
+ }
+
+ /**
+ * 数据读取后的处理
+ * @access protected
+ * @param array $data 当前数据
+ * @return array
+ */
+ protected function _read_data($data)
+ {
+ // 检查字段映射
+ if (!empty($this->_map) && C('READ_DATA_MAP')) {
+ foreach ($this->_map as $key => $val) {
+ if (isset($data[$val])) {
+ $data[$key] = $data[$val];
+ unset($data[$val]);
+ }
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * 查询数据
+ * @access public
+ * @param mixed $options 表达式参数
+ * @return mixed
+ */
+ public function find($options = array())
+ {
+ if (is_numeric($options) || is_string($options)) {
+ $where[$this->getPk()] = $options;
+ $options = array();
+ $options['where'] = $where;
+ }
+ // 根据复合主键查找记录
+ $pk = $this->getPk();
+ if (is_array($options) && (count($options) > 0) && is_array($pk)) {
+ // 根据复合主键查询
+ $count = 0;
+ foreach (array_keys($options) as $key) {
+ if (is_int($key)) {
+ $count++;
+ }
+
+ }
+ if (count($pk) == $count) {
+ $i = 0;
+ foreach ($pk as $field) {
+ $where[$field] = $options[$i];
+ unset($options[$i++]);
+ }
+ $options['where'] = $where;
+ } else {
+ return false;
+ }
+ }
+ // 总是查找一条记录
+ $options['limit'] = 1;
+ // 分析表达式
+ $options = $this->_parseOptions($options);
+ // 判断查询缓存
+ if (isset($options['cache'])) {
+ $cache = $options['cache'];
+ $key = is_string($cache['key']) ? $cache['key'] : md5(serialize($options));
+ $data = S($key, '', $cache);
+ if (false !== $data) {
+ $this->data = $data;
+ return $data;
+ }
+ }
+ $resultSet = $this->db->select($options);
+ if (false === $resultSet) {
+ return false;
+ }
+ if (empty($resultSet)) {
+ // 查询结果为空
+ return null;
+ }
+ if (is_string($resultSet)) {
+ return $resultSet;
+ }
+
+ // 读取数据后的处理
+ $data = $this->_read_data($resultSet[0]);
+ $this->_after_find($data, $options);
+ if (!empty($options['result'])) {
+ return $this->returnResult($data, $options['result']);
+ }
+ $this->data = $data;
+ if (isset($cache)) {
+ S($key, $data, $cache);
+ }
+ return $this->data;
+ }
+ // 查询成功的回调方法
+ protected function _after_find(&$result, $options)
+ {}
+
+ protected function returnResult($data, $type = '')
+ {
+ if ($type) {
+ if (is_callable($type)) {
+ return call_user_func($type, $data);
+ }
+ switch (strtolower($type)) {
+ case 'json':
+ return json_encode($data);
+ case 'xml':
+ return xml_encode($data);
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * 处理字段映射
+ * @access public
+ * @param array $data 当前数据
+ * @param integer $type 类型 0 写入 1 读取
+ * @return array
+ */
+ public function parseFieldsMap($data, $type = 1)
+ {
+ // 检查字段映射
+ if (!empty($this->_map)) {
+ foreach ($this->_map as $key => $val) {
+ if (1 == $type) {
+ // 读取
+ if (isset($data[$val])) {
+ $data[$key] = $data[$val];
+ unset($data[$val]);
+ }
+ } else {
+ if (isset($data[$key])) {
+ $data[$val] = $data[$key];
+ unset($data[$key]);
+ }
+ }
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * 设置记录的某个字段值
+ * 支持使用数据库字段和方法
+ * @access public
+ * @param string|array $field 字段名
+ * @param string $value 字段值
+ * @return boolean
+ */
+ public function setField($field, $value = '')
+ {
+ if (is_array($field)) {
+ $data = $field;
+ } else {
+ $data[$field] = $value;
+ }
+ return $this->save($data);
+ }
+
+ /**
+ * 字段值增长
+ * @access public
+ * @param string $field 字段名
+ * @param integer $step 增长值
+ * @param integer $lazyTime 延时时间(s)
+ * @return boolean
+ */
+ public function setInc($field, $step = 1, $lazyTime = 0)
+ {
+ if ($lazyTime > 0) {
+ // 延迟写入
+ $condition = $this->options['where'];
+ $guid = md5($this->name . '_' . $field . '_' . serialize($condition));
+ $step = $this->lazyWrite($guid, $step, $lazyTime);
+ if (empty($step)) {
+ return true; // 等待下次写入
+ } elseif ($step < 0) {
+ $step = '-' . $step;
+ }
+ }
+ return $this->setField($field, array('exp', $field . '+' . $step));
+ }
+
+ /**
+ * 字段值减少
+ * @access public
+ * @param string $field 字段名
+ * @param integer $step 减少值
+ * @param integer $lazyTime 延时时间(s)
+ * @return boolean
+ */
+ public function setDec($field, $step = 1, $lazyTime = 0)
+ {
+ if ($lazyTime > 0) {
+ // 延迟写入
+ $condition = $this->options['where'];
+ $guid = md5($this->name . '_' . $field . '_' . serialize($condition));
+ $step = $this->lazyWrite($guid, -$step, $lazyTime);
+ if (empty($step)) {
+ return true; // 等待下次写入
+ } elseif ($step > 0) {
+ $step = '-' . $step;
+ }
+ }
+ return $this->setField($field, array('exp', $field . '-' . $step));
+ }
+
+ /**
+ * 延时更新检查 返回false表示需要延时
+ * 否则返回实际写入的数值
+ * @access public
+ * @param string $guid 写入标识
+ * @param integer $step 写入步进值
+ * @param integer $lazyTime 延时时间(s)
+ * @return false|integer
+ */
+ protected function lazyWrite($guid, $step, $lazyTime)
+ {
+ if (false !== ($value = S($guid))) {
+ // 存在缓存写入数据
+ if (NOW_TIME > S($guid . '_time') + $lazyTime) {
+ // 延时更新时间到了,删除缓存数据 并实际写入数据库
+ S($guid, null);
+ S($guid . '_time', null);
+ return $value + $step;
+ } else {
+ // 追加数据到缓存
+ S($guid, $value + $step, 0);
+ return false;
+ }
+ } else {
+ // 没有缓存数据
+ S($guid, $step, 0);
+ // 计时开始
+ S($guid . '_time', NOW_TIME, 0);
+ return false;
+ }
+ }
+
+ /**
+ * 获取一条记录的某个字段值
+ * @access public
+ * @param string $field 字段名
+ * @param string $spea 字段数据间隔符号 NULL返回数组
+ * @return mixed
+ */
+ public function getField($field, $sepa = null)
+ {
+ $options['field'] = $field;
+ $options = $this->_parseOptions($options);
+ // 判断查询缓存
+ if (isset($options['cache'])) {
+ $cache = $options['cache'];
+ $key = is_string($cache['key']) ? $cache['key'] : md5($sepa . serialize($options));
+ $data = S($key, '', $cache);
+ if (false !== $data) {
+ return $data;
+ }
+ }
+ $field = trim($field);
+ if (strpos($field, ',') && false !== $sepa) {
+ // 多字段
+ if (!isset($options['limit'])) {
+ $options['limit'] = is_numeric($sepa) ? $sepa : '';
+ }
+ $resultSet = $this->db->select($options);
+ if (!empty($resultSet)) {
+ if (is_string($resultSet)) {
+ return $resultSet;
+ }
+ $_field = explode(',', $field);
+ $field = array_keys($resultSet[0]);
+ $key1 = array_shift($field);
+ $key2 = array_shift($field);
+ $cols = array();
+ $count = count($_field);
+ foreach ($resultSet as $result) {
+ $name = $result[$key1];
+ if (2 == $count) {
+ $cols[$name] = $result[$key2];
+ } else {
+ $cols[$name] = is_string($sepa) ? implode($sepa, array_slice($result, 1)) : $result;
+ }
+ }
+ if (isset($cache)) {
+ S($key, $cols, $cache);
+ }
+ return $cols;
+ }
+ } else {
+ // 查找一条记录
+ // 返回数据个数
+ if (true !== $sepa) {
+ // 当sepa指定为true的时候 返回所有数据
+ $options['limit'] = is_numeric($sepa) ? $sepa : 1;
+ }
+ $result = $this->db->select($options);
+ if (!empty($result)) {
+ if (is_string($result)) {
+ return $result;
+ }
+ if (true !== $sepa && 1 == $options['limit']) {
+ $data = reset($result[0]);
+ if (isset($cache)) {
+ S($key, $data, $cache);
+ }
+ return $data;
+ }
+ foreach ($result as $val) {
+ $array[] = reset($val);
+ }
+ if (isset($cache)) {
+ S($key, $array, $cache);
+ }
+ return $array;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 创建数据对象 但不保存到数据库
+ * @access public
+ * @param mixed $data 创建数据
+ * @param string $type 状态
+ * @return mixed
+ */
+ public function create($data = '', $type = '')
+ {
+ // 如果没有传值默认取POST数据
+ if (empty($data)) {
+ $data = I('post.');
+ } elseif (is_object($data)) {
+ $data = get_object_vars($data);
+ }
+ // 验证数据
+ if (empty($data) || !is_array($data)) {
+ $this->error = L('_DATA_TYPE_INVALID_');
+ return false;
+ }
+
+ // 状态
+ $type = $type ?: (!empty($data[$this->getPk()]) ? self::MODEL_UPDATE : self::MODEL_INSERT);
+
+ // 检查字段映射
+ $data = $this->parseFieldsMap($data, 0);
+
+ // 检测提交字段的合法性
+ if (isset($this->options['field'])) {
+ // $this->field('field1,field2...')->create()
+ $fields = $this->options['field'];
+ unset($this->options['field']);
+ } elseif (self::MODEL_INSERT == $type && isset($this->insertFields)) {
+ $fields = $this->insertFields;
+ } elseif (self::MODEL_UPDATE == $type && isset($this->updateFields)) {
+ $fields = $this->updateFields;
+ $pk = $this->getPk();
+ if (is_string($pk)) {
+ array_push($fields, $pk);
+ }
+ if (is_array($pk)) {
+ foreach ($pk as $pkTemp) {
+ array_push($fields, $pkTemp);
+ }
+ }
+ }
+ if (isset($fields)) {
+ if (is_string($fields)) {
+ $fields = explode(',', $fields);
+ }
+ // 判断令牌验证字段
+ if (C('TOKEN_ON')) {
+ $fields[] = C('TOKEN_NAME', null, '__hash__');
+ }
+
+ foreach ($data as $key => $val) {
+ if (!in_array($key, $fields)) {
+ unset($data[$key]);
+ }
+ }
+ }
+
+ // 数据自动验证
+ if (!$this->autoValidation($data, $type)) {
+ return false;
+ }
+
+ // 表单令牌验证
+ if (!$this->autoCheckToken($data)) {
+ $this->error = L('_TOKEN_ERROR_');
+ return false;
+ }
+
+ // 验证完成生成数据对象
+ if ($this->autoCheckFields) {
+ // 开启字段检测 则过滤非法字段数据
+ $fields = $this->getDbFields();
+ foreach ($data as $key => $val) {
+ if (!in_array($key, $fields)) {
+ unset($data[$key]);
+ } elseif (MAGIC_QUOTES_GPC && is_string($val)) {
+ $data[$key] = stripslashes($val);
+ }
+ }
+ }
+
+ // 创建完成对数据进行自动处理
+ $this->autoOperation($data, $type);
+ // 赋值当前数据对象
+ $this->data = $data;
+ // 返回创建的数据以供其他调用
+ return $data;
+ }
+
+ // 自动表单令牌验证
+ // TODO ajax无刷新多次提交暂不能满足
+ public function autoCheckToken($data)
+ {
+ // 支持使用token(false) 关闭令牌验证
+ if (isset($this->options['token']) && !$this->options['token']) {
+ return true;
+ }
+
+ if (C('TOKEN_ON')) {
+ $name = C('TOKEN_NAME', null, '__hash__');
+ if (!isset($data[$name]) || !isset($_SESSION[$name])) {
+ // 令牌数据无效
+ return false;
+ }
+
+ // 令牌验证
+ list($key, $value) = explode('_', $data[$name]);
+ if (isset($_SESSION[$name][$key]) && $value && $_SESSION[$name][$key] === $value) {
+ // 防止重复提交
+ unset($_SESSION[$name][$key]); // 验证完成销毁session
+ return true;
+ }
+ // 开启TOKEN重置
+ if (C('TOKEN_RESET')) {
+ unset($_SESSION[$name][$key]);
+ }
+
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 使用正则验证数据
+ * @access public
+ * @param string $value 要验证的数据
+ * @param string $rule 验证规则
+ * @return boolean
+ */
+ public function regex($value, $rule)
+ {
+ $validate = array(
+ 'require' => '/\S+/',
+ 'email' => '/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/',
+ 'url' => '/^http(s?):\/\/(?:[A-za-z0-9-]+\.)+[A-za-z]{2,4}(:\d+)?(?:[\/\?#][\/=\?%\-&~`@[\]\':+!\.#\w]*)?$/',
+ 'currency' => '/^\d+(\.\d+)?$/',
+ 'number' => '/^\d+$/',
+ 'zip' => '/^\d{6}$/',
+ 'integer' => '/^[-\+]?\d+$/',
+ 'double' => '/^[-\+]?\d+(\.\d+)?$/',
+ 'english' => '/^[A-Za-z]+$/',
+ );
+ // 检查是否有内置的正则表达式
+ if (isset($validate[strtolower($rule)])) {
+ $rule = $validate[strtolower($rule)];
+ }
+
+ return preg_match($rule, $value) === 1;
+ }
+
+ /**
+ * 自动表单处理
+ * @access public
+ * @param array $data 创建数据
+ * @param string $type 创建类型
+ * @return mixed
+ */
+ private function autoOperation(&$data, $type)
+ {
+ if (isset($this->options['auto']) && false === $this->options['auto']) {
+ // 关闭自动完成
+ return $data;
+ }
+ if (!empty($this->options['auto'])) {
+ $_auto = $this->options['auto'];
+ unset($this->options['auto']);
+ } elseif (!empty($this->_auto)) {
+ $_auto = $this->_auto;
+ }
+ // 自动填充
+ if (isset($_auto)) {
+ foreach ($_auto as $auto) {
+ // 填充因子定义格式
+ // array('field','填充内容','填充条件','附加规则',[额外参数])
+ if (empty($auto[2])) {
+ $auto[2] = self::MODEL_INSERT;
+ }
+ // 默认为新增的时候自动填充
+ if ($type == $auto[2] || self::MODEL_BOTH == $auto[2]) {
+ if (empty($auto[3])) {
+ $auto[3] = 'string';
+ }
+
+ switch (trim($auto[3])) {
+ case 'function':// 使用函数进行填充 字段的值作为参数
+ case 'callback': // 使用回调方法
+ $args = isset($auto[4]) ? (array) $auto[4] : array();
+ if (isset($data[$auto[0]])) {
+ array_unshift($args, $data[$auto[0]]);
+ }
+ if ('function' == $auto[3]) {
+ $data[$auto[0]] = call_user_func_array($auto[1], $args);
+ } else {
+ $data[$auto[0]] = call_user_func_array(array(&$this, $auto[1]), $args);
+ }
+ break;
+ case 'field': // 用其它字段的值进行填充
+ $data[$auto[0]] = $data[$auto[1]];
+ break;
+ case 'ignore': // 为空忽略
+ if ($auto[1] === $data[$auto[0]]) {
+ unset($data[$auto[0]]);
+ }
+
+ break;
+ case 'string':
+ default: // 默认作为字符串填充
+ $data[$auto[0]] = $auto[1];
+ }
+ if (isset($data[$auto[0]]) && false === $data[$auto[0]]) {
+ unset($data[$auto[0]]);
+ }
+
+ }
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * 自动表单验证
+ * @access protected
+ * @param array $data 创建数据
+ * @param string $type 创建类型
+ * @return boolean
+ */
+ protected function autoValidation($data, $type)
+ {
+ if (isset($this->options['validate']) && false === $this->options['validate']) {
+ // 关闭自动验证
+ return true;
+ }
+ if (!empty($this->options['validate'])) {
+ $_validate = $this->options['validate'];
+ unset($this->options['validate']);
+ } elseif (!empty($this->_validate)) {
+ $_validate = $this->_validate;
+ }
+ // 属性验证
+ if (isset($_validate)) {
+ // 如果设置了数据自动验证则进行数据验证
+ if ($this->patchValidate) {
+ // 重置验证错误信息
+ $this->error = array();
+ }
+ foreach ($_validate as $key => $val) {
+ // 验证因子定义格式
+ // array(field,rule,message,condition,type,when,params)
+ // 判断是否需要执行验证
+ if (empty($val[5]) || (self::MODEL_BOTH == $val[5] && $type < 3) || $val[5] == $type) {
+ if (0 == strpos($val[2], '{%') && strpos($val[2], '}'))
+ // 支持提示信息的多语言 使用 {%语言定义} 方式
+ {
+ $val[2] = L(substr($val[2], 2, -1));
+ }
+
+ $val[3] = isset($val[3]) ? $val[3] : self::EXISTS_VALIDATE;
+ $val[4] = isset($val[4]) ? $val[4] : 'regex';
+ // 判断验证条件
+ switch ($val[3]) {
+ case self::MUST_VALIDATE: // 必须验证 不管表单是否有设置该字段
+ if (false === $this->_validationField($data, $val)) {
+ return false;
+ }
+
+ break;
+ case self::VALUE_VALIDATE: // 值不为空的时候才验证
+ if ('' != trim($data[$val[0]])) {
+ if (false === $this->_validationField($data, $val)) {
+ return false;
+ }
+ }
+
+ break;
+ default: // 默认表单存在该字段就验证
+ if (isset($data[$val[0]])) {
+ if (false === $this->_validationField($data, $val)) {
+ return false;
+ }
+ }
+
+ }
+ }
+ }
+ // 批量验证的时候最后返回错误
+ if (!empty($this->error)) {
+ return false;
+ }
+
+ }
+ return true;
+ }
+
+ /**
+ * 验证表单字段 支持批量验证
+ * 如果批量验证返回错误的数组信息
+ * @access protected
+ * @param array $data 创建数据
+ * @param array $val 验证因子
+ * @return boolean
+ */
+ protected function _validationField($data, $val)
+ {
+ if ($this->patchValidate && isset($this->error[$val[0]])) {
+ //当前字段已经有规则验证没有通过
+ return;
+ }
+
+ if (false === $this->_validationFieldItem($data, $val)) {
+ if ($this->patchValidate) {
+ $this->error[$val[0]] = $val[2];
+ } else {
+ $this->error = $val[2];
+ return false;
+ }
+ }
+ return;
+ }
+
+ /**
+ * 根据验证因子验证字段
+ * @access protected
+ * @param array $data 创建数据
+ * @param array $val 验证因子
+ * @return boolean
+ */
+ protected function _validationFieldItem($data, $val)
+ {
+ switch (strtolower(trim($val[4]))) {
+ case 'function':// 使用函数进行验证
+ case 'callback': // 调用方法进行验证
+ $args = isset($val[6]) ? (array) $val[6] : array();
+ if (is_string($val[0]) && strpos($val[0], ',')) {
+ $val[0] = explode(',', $val[0]);
+ }
+
+ if (is_array($val[0])) {
+ // 支持多个字段验证
+ foreach ($val[0] as $field) {
+ $_data[$field] = $data[$field];
+ }
+
+ array_unshift($args, $_data);
+ } else {
+ array_unshift($args, $data[$val[0]]);
+ }
+ if ('function' == $val[4]) {
+ return call_user_func_array($val[1], $args);
+ } else {
+ return call_user_func_array(array(&$this, $val[1]), $args);
+ }
+ case 'confirm': // 验证两个字段是否相同
+ return $data[$val[0]] == $data[$val[1]];
+ case 'unique': // 验证某个值是否唯一
+ if (is_string($val[0]) && strpos($val[0], ',')) {
+ $val[0] = explode(',', $val[0]);
+ }
+
+ $map = array();
+ if (is_array($val[0])) {
+ // 支持多个字段验证
+ foreach ($val[0] as $field) {
+ $map[$field] = $data[$field];
+ }
+
+ } else {
+ $map[$val[0]] = $data[$val[0]];
+ }
+ $pk = $this->getPk();
+ if (!empty($data[$pk]) && is_string($pk)) {
+ // 完善编辑的时候验证唯一
+ $map[$pk] = array('neq', $data[$pk]);
+ }
+ $options = $this->options;
+ if ($this->where($map)->find()) {
+ return false;
+ }
+
+ $this->options = $options;
+ return true;
+ default: // 检查附加规则
+ return $this->check($data[$val[0]], $val[1], $val[4]);
+ }
+ }
+
+ /**
+ * 验证数据 支持 in between equal length regex expire ip_allow ip_deny
+ * @access public
+ * @param string $value 验证数据
+ * @param mixed $rule 验证表达式
+ * @param string $type 验证方式 默认为正则验证
+ * @return boolean
+ */
+ public function check($value, $rule, $type = 'regex')
+ {
+ $type = strtolower(trim($type));
+ switch ($type) {
+ case 'in':// 验证是否在某个指定范围之内 逗号分隔字符串或者数组
+ case 'notin':
+ $range = is_array($rule) ? $rule : explode(',', $rule);
+ return 'in' == $type ? in_array($value, $range) : !in_array($value, $range);
+ case 'between':// 验证是否在某个范围
+ case 'notbetween': // 验证是否不在某个范围
+ if (is_array($rule)) {
+ $min = $rule[0];
+ $max = $rule[1];
+ } else {
+ list($min, $max) = explode(',', $rule);
+ }
+ return 'between' == $type ? $value >= $min && $value <= $max : $value < $min || $value > $max;
+ case 'equal':// 验证是否等于某个值
+ case 'notequal': // 验证是否等于某个值
+ return 'equal' == $type ? $value == $rule : $value != $rule;
+ case 'length': // 验证长度
+ $length = mb_strlen($value, 'utf-8'); // 当前数据长度
+ if (strpos($rule, ',')) {
+ // 长度区间
+ list($min, $max) = explode(',', $rule);
+ return $length >= $min && $length <= $max;
+ } else {
+ // 指定长度
+ return $length == $rule;
+ }
+ case 'expire':
+ list($start, $end) = explode(',', $rule);
+ if (!is_numeric($start)) {
+ $start = strtotime($start);
+ }
+
+ if (!is_numeric($end)) {
+ $end = strtotime($end);
+ }
+
+ return NOW_TIME >= $start && NOW_TIME <= $end;
+ case 'ip_allow': // IP 操作许可验证
+ return in_array(get_client_ip(), explode(',', $rule));
+ case 'ip_deny': // IP 操作禁止验证
+ return !in_array(get_client_ip(), explode(',', $rule));
+ case 'regex':
+ default: // 默认使用正则验证 可以使用验证类中定义的验证名称
+ // 检查附加规则
+ return $this->regex($value, $rule);
+ }
+ }
+
+ /**
+ * 存储过程返回多数据集
+ * @access public
+ * @param string $sql SQL指令
+ * @param mixed $parse 是否需要解析SQL
+ * @return array
+ */
+ public function procedure($sql, $parse = false)
+ {
+ return $this->db->procedure($sql, $parse);
+ }
+
+ /**
+ * SQL查询
+ * @access public
+ * @param string $sql SQL指令
+ * @param mixed $parse 是否需要解析SQL
+ * @return mixed
+ */
+ public function query($sql, $parse = false)
+ {
+ if (!is_bool($parse) && !is_array($parse)) {
+ $parse = func_get_args();
+ array_shift($parse);
+ }
+ $sql = $this->parseSql($sql, $parse);
+ return $this->db->query($sql);
+ }
+
+ /**
+ * 执行SQL语句
+ * @access public
+ * @param string $sql SQL指令
+ * @param mixed $parse 是否需要解析SQL
+ * @return false | integer
+ */
+ public function execute($sql, $parse = false)
+ {
+ if (!is_bool($parse) && !is_array($parse)) {
+ $parse = func_get_args();
+ array_shift($parse);
+ }
+ $sql = $this->parseSql($sql, $parse);
+ return $this->db->execute($sql);
+ }
+
+ /**
+ * 解析SQL语句
+ * @access public
+ * @param string $sql SQL指令
+ * @param boolean $parse 是否需要解析SQL
+ * @return string
+ */
+ protected function parseSql($sql, $parse)
+ {
+ // 分析表达式
+ if (true === $parse) {
+ $options = $this->_parseOptions();
+ $sql = $this->db->parseSql($sql, $options);
+ } elseif (is_array($parse)) {
+ // SQL预处理
+ $parse = array_map(array($this->db, 'escapeString'), $parse);
+ $sql = vsprintf($sql, $parse);
+ } else {
+ $sql = strtr($sql, array('__TABLE__' => $this->getTableName(), '__PREFIX__' => $this->tablePrefix));
+ $prefix = $this->tablePrefix;
+ $sql = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) {return $prefix . strtolower($match[1]);}, $sql);
+ }
+ $this->db->setModel($this->name);
+ return $sql;
+ }
+
+ /**
+ * 切换当前的数据库连接
+ * @access public
+ * @param integer $linkNum 连接序号
+ * @param mixed $config 数据库连接信息
+ * @param boolean $force 强制重新连接
+ * @return Model
+ */
+ public function db($linkNum = '', $config = '', $force = false)
+ {
+ if ('' === $linkNum && $this->db) {
+ return $this->db;
+ }
+
+ if (!isset($this->_db[$linkNum]) || $force) {
+ // 创建一个新的实例
+ if (!empty($config) && is_string($config) && false === strpos($config, '/')) {
+ // 支持读取配置参数
+ $config = C($config);
+ }
+ $this->_db[$linkNum] = Db::getInstance($config);
+ } elseif (null === $config) {
+ $this->_db[$linkNum]->close(); // 关闭数据库连接
+ unset($this->_db[$linkNum]);
+ return;
+ }
+
+ // 切换数据库连接
+ $this->db = $this->_db[$linkNum];
+ $this->_after_db();
+ // 字段检测
+ if (!empty($this->name) && $this->autoCheckFields) {
+ $this->_checkTableInfo();
+ }
+
+ return $this;
+ }
+ // 数据库切换后回调方法
+ protected function _after_db()
+ {}
+
+ /**
+ * 得到当前的数据对象名称
+ * @access public
+ * @return string
+ */
+ public function getModelName()
+ {
+ if (empty($this->name)) {
+ $name = substr(get_class($this), 0, -strlen(C('DEFAULT_M_LAYER')));
+ if ($pos = strrpos($name, '\\')) {
+ //有命名空间
+ $this->name = substr($name, $pos + 1);
+ } else {
+ $this->name = $name;
+ }
+ }
+ return $this->name;
+ }
+
+ /**
+ * 得到完整的数据表名
+ * @access public
+ * @return string
+ */
+ public function getTableName()
+ {
+ if (empty($this->trueTableName)) {
+ $tableName = !empty($this->tablePrefix) ? $this->tablePrefix : '';
+ if (!empty($this->tableName)) {
+ $tableName .= $this->tableName;
+ } else {
+ $tableName .= parse_name($this->name);
+ }
+ $this->trueTableName = strtolower($tableName);
+ }
+ return (!empty($this->dbName) ? $this->dbName . '.' : '') . $this->trueTableName;
+ }
+
+ /**
+ * 启动事务
+ * @access public
+ * @return void
+ */
+ public function startTrans()
+ {
+ $this->commit();
+ $this->db->startTrans();
+ return;
+ }
+
+ /**
+ * 提交事务
+ * @access public
+ * @return boolean
+ */
+ public function commit()
+ {
+ return $this->db->commit();
+ }
+
+ /**
+ * 事务回滚
+ * @access public
+ * @return boolean
+ */
+ public function rollback()
+ {
+ return $this->db->rollback();
+ }
+
+ /**
+ * 返回模型的错误信息
+ * @access public
+ * @return string
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * 返回数据库的错误信息
+ * @access public
+ * @return string
+ */
+ public function getDbError()
+ {
+ return $this->db->getError();
+ }
+
+ /**
+ * 返回最后插入的ID
+ * @access public
+ * @return string
+ */
+ public function getLastInsID()
+ {
+ return $this->db->getLastInsID();
+ }
+
+ /**
+ * 返回最后执行的sql语句
+ * @access public
+ * @return string
+ */
+ public function getLastSql()
+ {
+ return $this->db->getLastSql($this->name);
+ }
+ // 鉴于getLastSql比较常用 增加_sql 别名
+ public function _sql()
+ {
+ return $this->getLastSql();
+ }
+
+ /**
+ * 获取主键名称
+ * @access public
+ * @return string
+ */
+ public function getPk()
+ {
+ return $this->pk;
+ }
+
+ /**
+ * 获取数据表字段信息
+ * @access public
+ * @return array
+ */
+ public function getDbFields()
+ {
+ if (isset($this->options['table'])) {
+ // 动态指定表名
+ if (is_array($this->options['table'])) {
+ $table = key($this->options['table']);
+ } else {
+ $table = $this->options['table'];
+ if (strpos($table, ')')) {
+ // 子查询
+ return false;
+ }
+ }
+ $fields = $this->db->getFields($table);
+ return $fields ? array_keys($fields) : false;
+ }
+ if ($this->fields) {
+ $fields = $this->fields;
+ unset($fields['_type'], $fields['_pk']);
+ return $fields;
+ }
+ return false;
+ }
+
+ /**
+ * 设置数据对象值
+ * @access public
+ * @param mixed $data 数据
+ * @return Model
+ */
+ public function data($data = '')
+ {
+ if ('' === $data && !empty($this->data)) {
+ return $this->data;
+ }
+ if (is_object($data)) {
+ $data = get_object_vars($data);
+ } elseif (is_string($data)) {
+ parse_str($data, $data);
+ } elseif (!is_array($data)) {
+ E(L('_DATA_TYPE_INVALID_'));
+ }
+ $this->data = $data;
+ return $this;
+ }
+
+ /**
+ * 指定当前的数据表
+ * @access public
+ * @param mixed $table
+ * @return Model
+ */
+ public function table($table)
+ {
+ $prefix = $this->tablePrefix;
+ if (is_array($table)) {
+ $this->options['table'] = $table;
+ } elseif (!empty($table)) {
+ //将__TABLE_NAME__替换成带前缀的表名
+ $table = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) {return $prefix . strtolower($match[1]);}, $table);
+ $this->options['table'] = $table;
+ }
+ return $this;
+ }
+
+ /**
+ * USING支持 用于多表删除
+ * @access public
+ * @param mixed $using
+ * @return Model
+ */
+ public function using($using)
+ {
+ $prefix = $this->tablePrefix;
+ if (is_array($using)) {
+ $this->options['using'] = $using;
+ } elseif (!empty($using)) {
+ //将__TABLE_NAME__替换成带前缀的表名
+ $using = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) {return $prefix . strtolower($match[1]);}, $using);
+ $this->options['using'] = $using;
+ }
+ return $this;
+ }
+
+ /**
+ * 查询SQL组装 join
+ * @access public
+ * @param mixed $join
+ * @param string $type JOIN类型
+ * @return Model
+ */
+ public function join($join, $type = 'INNER')
+ {
+ $prefix = $this->tablePrefix;
+ if (is_array($join)) {
+ foreach ($join as $key => &$_join) {
+ $_join = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) {return $prefix . strtolower($match[1]);}, $_join);
+ $_join = false !== stripos($_join, 'JOIN') ? $_join : $type . ' JOIN ' . $_join;
+ }
+ $this->options['join'] = $join;
+ } elseif (!empty($join)) {
+ //将__TABLE_NAME__字符串替换成带前缀的表名
+ $join = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) {return $prefix . strtolower($match[1]);}, $join);
+ $this->options['join'][] = false !== stripos($join, 'JOIN') ? $join : $type . ' JOIN ' . $join;
+ }
+ return $this;
+ }
+
+ /**
+ * 查询SQL组装 union
+ * @access public
+ * @param mixed $union
+ * @param boolean $all
+ * @return Model
+ */
+ public function union($union, $all = false)
+ {
+ if (empty($union)) {
+ return $this;
+ }
+
+ if ($all) {
+ $this->options['union']['_all'] = true;
+ }
+ if (is_object($union)) {
+ $union = get_object_vars($union);
+ }
+ // 转换union表达式
+ if (is_string($union)) {
+ $prefix = $this->tablePrefix;
+ //将__TABLE_NAME__字符串替换成带前缀的表名
+ $options = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) {return $prefix . strtolower($match[1]);}, $union);
+ } elseif (is_array($union)) {
+ if (isset($union[0])) {
+ $this->options['union'] = array_merge($this->options['union'], $union);
+ return $this;
+ } else {
+ $options = $union;
+ }
+ } else {
+ E(L('_DATA_TYPE_INVALID_'));
+ }
+ $this->options['union'][] = $options;
+ return $this;
+ }
+
+ /**
+ * 查询缓存
+ * @access public
+ * @param mixed $key
+ * @param integer $expire
+ * @param string $type
+ * @return Model
+ */
+ public function cache($key = true, $expire = null, $type = '')
+ {
+ // 增加快捷调用方式 cache(10) 等同于 cache(true, 10)
+ if (is_numeric($key) && is_null($expire)) {
+ $expire = $key;
+ $key = true;
+ }
+ if (false !== $key) {
+ $this->options['cache'] = array('key' => $key, 'expire' => $expire, 'type' => $type);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 指定查询字段 支持字段排除
+ * @access public
+ * @param mixed $field
+ * @param boolean $except 是否排除
+ * @return Model
+ */
+ public function field($field, $except = false)
+ {
+ if (true === $field) {
+ // 获取全部字段
+ $fields = $this->getDbFields();
+ $field = $fields ?: '*';
+ } elseif ($except) {
+ // 字段排除
+ if (is_string($field)) {
+ $field = explode(',', $field);
+ }
+ $fields = $this->getDbFields();
+ $field = $fields ? array_diff($fields, $field) : $field;
+ }
+ $this->options['field'] = $field;
+ return $this;
+ }
+
+ /**
+ * 调用命名范围
+ * @access public
+ * @param mixed $scope 命名范围名称 支持多个 和直接定义
+ * @param array $args 参数
+ * @return Model
+ */
+ public function scope($scope = '', $args = null)
+ {
+ if ('' === $scope) {
+ if (isset($this->_scope['default'])) {
+ // 默认的命名范围
+ $options = $this->_scope['default'];
+ } else {
+ return $this;
+ }
+ } elseif (is_string($scope)) {
+ // 支持多个命名范围调用 用逗号分割
+ $scopes = explode(',', $scope);
+ $options = array();
+ foreach ($scopes as $name) {
+ if (!isset($this->_scope[$name])) {
+ continue;
+ }
+
+ $options = array_merge($options, $this->_scope[$name]);
+ }
+ if (!empty($args) && is_array($args)) {
+ $options = array_merge($options, $args);
+ }
+ } elseif (is_array($scope)) {
+ // 直接传入命名范围定义
+ $options = $scope;
+ }
+
+ if (is_array($options) && !empty($options)) {
+ $this->options = array_merge($this->options, array_change_key_case($options));
+ }
+ return $this;
+ }
+
+ /**
+ * 指定查询条件 支持安全过滤
+ * @access public
+ * @param mixed $where 条件表达式
+ * @param mixed $parse 预处理参数
+ * @return Model
+ */
+ public function where($where, $parse = null)
+ {
+ if (!is_null($parse) && is_string($where)) {
+ if (!is_array($parse)) {
+ $parse = func_get_args();
+ array_shift($parse);
+ }
+ $parse = array_map(array($this->db, 'escapeString'), $parse);
+ $where = vsprintf($where, $parse);
+ } elseif (is_object($where)) {
+ $where = get_object_vars($where);
+ }
+ if (is_string($where) && '' != $where) {
+ $map = array();
+ $map['_string'] = $where;
+ $where = $map;
+ }
+ if (isset($this->options['where'])) {
+ $this->options['where'] = array_merge($this->options['where'], $where);
+ } else {
+ $this->options['where'] = $where;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 指定查询数量
+ * @access public
+ * @param mixed $offset 起始位置
+ * @param mixed $length 查询数量
+ * @return Model
+ */
+ public function limit($offset, $length = null)
+ {
+ if (is_null($length) && strpos($offset, ',')) {
+ list($offset, $length) = explode(',', $offset);
+ }
+ $this->options['limit'] = intval($offset) . ($length ? ',' . intval($length) : '');
+ return $this;
+ }
+
+ /**
+ * 指定分页
+ * @access public
+ * @param mixed $page 页数
+ * @param mixed $listRows 每页数量
+ * @return Model
+ */
+ public function page($page, $listRows = null)
+ {
+ if (is_null($listRows) && strpos($page, ',')) {
+ list($page, $listRows) = explode(',', $page);
+ }
+ $this->options['page'] = array(intval($page), intval($listRows));
+ return $this;
+ }
+
+ /**
+ * 查询注释
+ * @access public
+ * @param string $comment 注释
+ * @return Model
+ */
+ public function comment($comment)
+ {
+ $this->options['comment'] = $comment;
+ return $this;
+ }
+
+ /**
+ * 获取执行的SQL语句
+ * @access public
+ * @param boolean $fetch 是否返回sql
+ * @return Model
+ */
+ public function fetchSql($fetch = true)
+ {
+ $this->options['fetch_sql'] = $fetch;
+ return $this;
+ }
+
+ /**
+ * 参数绑定
+ * @access public
+ * @param string $key 参数名
+ * @param mixed $value 绑定的变量及绑定参数
+ * @return Model
+ */
+ public function bind($key, $value = false)
+ {
+ if (is_array($key)) {
+ $this->options['bind'] = $key;
+ } else {
+ $num = func_num_args();
+ if ($num > 2) {
+ $params = func_get_args();
+ array_shift($params);
+ $this->options['bind'][$key] = $params;
+ } else {
+ $this->options['bind'][$key] = $value;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * 设置模型的属性值
+ * @access public
+ * @param string $name 名称
+ * @param mixed $value 值
+ * @return Model
+ */
+ public function setProperty($name, $value)
+ {
+ if (property_exists($this, $name)) {
+ $this->$name = $value;
+ }
+
+ return $this;
+ }
+
+}
diff --git a/Framework/Library/Think/Model/AdvModel.class.php b/Framework/Library/Think/Model/AdvModel.class.php
new file mode 100644
index 00000000..3664e035
--- /dev/null
+++ b/Framework/Library/Think/Model/AdvModel.class.php
@@ -0,0 +1,656 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Model;
+
+use Think\Model;
+
+/**
+ * 高级模型扩展
+ */
+class AdvModel extends Model
+{
+ protected $optimLock = 'lock_version';
+ protected $returnType = 'array';
+ protected $blobFields = array();
+ protected $blobValues = null;
+ protected $serializeField = array();
+ protected $readonlyField = array();
+ protected $_filter = array();
+ protected $partition = array();
+
+ public function __construct($name = '', $tablePrefix = '', $connection = '')
+ {
+ if ('' !== $name || is_subclass_of($this, 'AdvModel')) {
+ // 如果是AdvModel子类或者有传入模型名称则获取字段缓存
+ } else {
+ // 空的模型 关闭字段缓存
+ $this->autoCheckFields = false;
+ }
+ parent::__construct($name, $tablePrefix, $connection);
+ }
+
+ /**
+ * 利用__call方法重载 实现一些特殊的Model方法 (魔术方法)
+ * @access public
+ * @param string $method 方法名称
+ * @param mixed $args 调用参数
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ if (strtolower(substr($method, 0, 3)) == 'top') {
+ // 获取前N条记录
+ $count = substr($method, 3);
+ array_unshift($args, $count);
+ return call_user_func_array(array(&$this, 'topN'), $args);
+ } else {
+ return parent::__call($method, $args);
+ }
+ }
+
+ /**
+ * 对保存到数据库的数据进行处理
+ * @access protected
+ * @param mixed $data 要操作的数据
+ * @return boolean
+ */
+ protected function _facade($data)
+ {
+ // 检查序列化字段
+ $data = $this->serializeField($data);
+ return parent::_facade($data);
+ }
+
+ // 查询成功后的回调方法
+ protected function _after_find(&$result, $options = '')
+ {
+ // 检查序列化字段
+ $this->checkSerializeField($result);
+ // 获取文本字段
+ $this->getBlobFields($result);
+ // 检查字段过滤
+ $result = $this->getFilterFields($result);
+ // 缓存乐观锁
+ $this->cacheLockVersion($result);
+ }
+
+ // 查询数据集成功后的回调方法
+ protected function _after_select(&$resultSet, $options = '')
+ {
+ // 检查序列化字段
+ $resultSet = $this->checkListSerializeField($resultSet);
+ // 获取文本字段
+ $resultSet = $this->getListBlobFields($resultSet);
+ // 检查列表字段过滤
+ $resultSet = $this->getFilterListFields($resultSet);
+ }
+
+ // 写入前的回调方法
+ protected function _before_insert(&$data, $options = '')
+ {
+ // 记录乐观锁
+ $data = $this->recordLockVersion($data);
+ // 检查文本字段
+ $data = $this->checkBlobFields($data);
+ // 检查字段过滤
+ $data = $this->setFilterFields($data);
+ }
+
+ protected function _after_insert($data, $options)
+ {
+ // 保存文本字段
+ $this->saveBlobFields($data);
+ }
+
+ // 更新前的回调方法
+ protected function _before_update(&$data, $options = '')
+ {
+ // 检查乐观锁
+ $pk = $this->getPK();
+ if (isset($options['where'][$pk])) {
+ $id = $options['where'][$pk];
+ if (!$this->checkLockVersion($id, $data)) {
+ return false;
+ }
+ }
+ // 检查文本字段
+ $data = $this->checkBlobFields($data);
+ // 检查只读字段
+ $data = $this->checkReadonlyField($data);
+ // 检查字段过滤
+ $data = $this->setFilterFields($data);
+ }
+
+ protected function _after_update($data, $options)
+ {
+ // 保存文本字段
+ $this->saveBlobFields($data);
+ }
+
+ protected function _after_delete($data, $options)
+ {
+ // 删除Blob数据
+ $this->delBlobFields($data);
+ }
+
+ /**
+ * 记录乐观锁
+ * @access protected
+ * @param array $data 数据对象
+ * @return array
+ */
+ protected function recordLockVersion($data)
+ {
+ // 记录乐观锁
+ if ($this->optimLock && !isset($data[$this->optimLock])) {
+ if (in_array($this->optimLock, $this->fields, true)) {
+ $data[$this->optimLock] = 0;
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * 缓存乐观锁
+ * @access protected
+ * @param array $data 数据对象
+ * @return void
+ */
+ protected function cacheLockVersion($data)
+ {
+ if ($this->optimLock) {
+ if (isset($data[$this->optimLock]) && isset($data[$this->getPk()])) {
+ // 只有当存在乐观锁字段和主键有值的时候才记录乐观锁
+ $_SESSION[$this->name . '_' . $data[$this->getPk()] . '_lock_version'] = $data[$this->optimLock];
+ }
+ }
+ }
+
+ /**
+ * 检查乐观锁
+ * @access protected
+ * @param inteter $id 当前主键
+ * @param array $data 当前数据
+ * @return mixed
+ */
+ protected function checkLockVersion($id, &$data)
+ {
+ // 检查乐观锁
+ $identify = $this->name . '_' . $id . '_lock_version';
+ if ($this->optimLock && isset($_SESSION[$identify])) {
+ $lock_version = $_SESSION[$identify];
+ $vo = $this->field($this->optimLock)->find($id);
+ $_SESSION[$identify] = $lock_version;
+ $curr_version = $vo[$this->optimLock];
+ if (isset($curr_version)) {
+ if ($curr_version > 0 && $lock_version != $curr_version) {
+ // 记录已经更新
+ $this->error = L('_RECORD_HAS_UPDATE_');
+ return false;
+ } else {
+ // 更新乐观锁
+ $save_version = $data[$this->optimLock];
+ if ($save_version != $lock_version + 1) {
+ $data[$this->optimLock] = $lock_version + 1;
+ }
+ $_SESSION[$identify] = $lock_version + 1;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 查找前N个记录
+ * @access public
+ * @param integer $count 记录个数
+ * @param array $options 查询表达式
+ * @return array
+ */
+ public function topN($count, $options = array())
+ {
+ $options['limit'] = $count;
+ return $this->select($options);
+ }
+
+ /**
+ * 查询符合条件的第N条记录
+ * 0 表示第一条记录 -1 表示最后一条记录
+ * @access public
+ * @param integer $position 记录位置
+ * @param array $options 查询表达式
+ * @return mixed
+ */
+ public function getN($position = 0, $options = array())
+ {
+ if ($position >= 0) {
+ // 正向查找
+ $options['limit'] = $position . ',1';
+ $list = $this->select($options);
+ return $list ? $list[0] : false;
+ } else {
+ // 逆序查找
+ $list = $this->select($options);
+ return $list ? $list[count($list) - abs($position)] : false;
+ }
+ }
+
+ /**
+ * 获取满足条件的第一条记录
+ * @access public
+ * @param array $options 查询表达式
+ * @return mixed
+ */
+ public function first($options = array())
+ {
+ return $this->getN(0, $options);
+ }
+
+ /**
+ * 获取满足条件的最后一条记录
+ * @access public
+ * @param array $options 查询表达式
+ * @return mixed
+ */
+ public function last($options = array())
+ {
+ return $this->getN(-1, $options);
+ }
+
+ /**
+ * 返回数据
+ * @access public
+ * @param array $data 数据
+ * @param string $type 返回类型 默认为数组
+ * @return mixed
+ */
+ public function returnResult($data, $type = '')
+ {
+ if ('' === $type) {
+ $type = $this->returnType;
+ }
+
+ switch ($type) {
+ case 'array':return $data;
+ case 'object':return (object) $data;
+ default: // 允许用户自定义返回类型
+ if (class_exists($type)) {
+ return new $type($data);
+ } else {
+ E(L('_CLASS_NOT_EXIST_') . ':' . $type);
+ }
+
+ }
+ }
+
+ /**
+ * 获取数据的时候过滤数据字段
+ * @access protected
+ * @param mixed $result 查询的数据
+ * @return array
+ */
+ protected function getFilterFields(&$result)
+ {
+ if (!empty($this->_filter)) {
+ foreach ($this->_filter as $field => $filter) {
+ if (isset($result[$field])) {
+ $fun = $filter[1];
+ if (!empty($fun)) {
+ if (isset($filter[2]) && $filter[2]) {
+ // 传递整个数据对象作为参数
+ $result[$field] = call_user_func($fun, $result);
+ } else {
+ // 传递字段的值作为参数
+ $result[$field] = call_user_func($fun, $result[$field]);
+ }
+ }
+ }
+ }
+ }
+ return $result;
+ }
+
+ protected function getFilterListFields(&$resultSet)
+ {
+ if (!empty($this->_filter)) {
+ foreach ($resultSet as $key => $result) {
+ $resultSet[$key] = $this->getFilterFields($result);
+ }
+
+ }
+ return $resultSet;
+ }
+
+ /**
+ * 写入数据的时候过滤数据字段
+ * @access protected
+ * @param mixed $result 查询的数据
+ * @return array
+ */
+ protected function setFilterFields($data)
+ {
+ if (!empty($this->_filter)) {
+ foreach ($this->_filter as $field => $filter) {
+ if (isset($data[$field])) {
+ $fun = $filter[0];
+ if (!empty($fun)) {
+ if (isset($filter[2]) && $filter[2]) {
+ // 传递整个数据对象作为参数
+ $data[$field] = call_user_func($fun, $data);
+ } else {
+ // 传递字段的值作为参数
+ $data[$field] = call_user_func($fun, $data[$field]);
+ }
+ }
+ }
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * 返回数据列表
+ * @access protected
+ * @param array $resultSet 数据
+ * @param string $type 返回类型 默认为数组
+ * @return void
+ */
+ protected function returnResultSet(&$resultSet, $type = '')
+ {
+ foreach ($resultSet as $key => $data) {
+ $resultSet[$key] = $this->returnResult($data, $type);
+ }
+
+ return $resultSet;
+ }
+
+ protected function checkBlobFields(&$data)
+ {
+ // 检查Blob文件保存字段
+ if (!empty($this->blobFields)) {
+ foreach ($this->blobFields as $field) {
+ if (isset($data[$field])) {
+ if (isset($data[$this->getPk()])) {
+ $this->blobValues[$this->name . '/' . $data[$this->getPk()] . '_' . $field] = $data[$field];
+ } else {
+ $this->blobValues[$this->name . '/@?id@_' . $field] = $data[$field];
+ }
+
+ unset($data[$field]);
+ }
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * 获取数据集的文本字段
+ * @access protected
+ * @param mixed $resultSet 查询的数据
+ * @param string $field 查询的字段
+ * @return void
+ */
+ protected function getListBlobFields(&$resultSet, $field = '')
+ {
+ if (!empty($this->blobFields)) {
+ foreach ($resultSet as $key => $result) {
+ $result = $this->getBlobFields($result, $field);
+ $resultSet[$key] = $result;
+ }
+ }
+ return $resultSet;
+ }
+
+ /**
+ * 获取数据的文本字段
+ * @access protected
+ * @param mixed $data 查询的数据
+ * @param string $field 查询的字段
+ * @return void
+ */
+ protected function getBlobFields(&$data, $field = '')
+ {
+ if (!empty($this->blobFields)) {
+ $pk = $this->getPk();
+ $id = $data[$pk];
+ if (empty($field)) {
+ foreach ($this->blobFields as $field) {
+ $identify = $this->name . '/' . $id . '_' . $field;
+ $data[$field] = F($identify);
+ }
+ return $data;
+ } else {
+ $identify = $this->name . '/' . $id . '_' . $field;
+ return F($identify);
+ }
+ }
+ }
+
+ /**
+ * 保存File方式的字段
+ * @access protected
+ * @param mixed $data 保存的数据
+ * @return void
+ */
+ protected function saveBlobFields(&$data)
+ {
+ if (!empty($this->blobFields)) {
+ foreach ($this->blobValues as $key => $val) {
+ if (strpos($key, '@?id@')) {
+ $key = str_replace('@?id@', $data[$this->getPk()], $key);
+ }
+
+ F($key, $val);
+ }
+ }
+ }
+
+ /**
+ * 删除File方式的字段
+ * @access protected
+ * @param mixed $data 保存的数据
+ * @param string $field 查询的字段
+ * @return void
+ */
+ protected function delBlobFields(&$data, $field = '')
+ {
+ if (!empty($this->blobFields)) {
+ $pk = $this->getPk();
+ $id = $data[$pk];
+ if (empty($field)) {
+ foreach ($this->blobFields as $field) {
+ $identify = $this->name . '/' . $id . '_' . $field;
+ F($identify, null);
+ }
+ } else {
+ $identify = $this->name . '/' . $id . '_' . $field;
+ F($identify, null);
+ }
+ }
+ }
+
+ /**
+ * 检查序列化数据字段
+ * @access protected
+ * @param array $data 数据
+ * @return array
+ */
+ protected function serializeField(&$data)
+ {
+ // 检查序列化字段
+ if (!empty($this->serializeField)) {
+ // 定义方式 $this->serializeField = array('ser'=>array('name','email'));
+ foreach ($this->serializeField as $key => $val) {
+ if (empty($data[$key])) {
+ $serialize = array();
+ foreach ($val as $name) {
+ if (isset($data[$name])) {
+ $serialize[$name] = $data[$name];
+ unset($data[$name]);
+ }
+ }
+ if (!empty($serialize)) {
+ $data[$key] = serialize($serialize);
+ }
+ }
+ }
+ }
+ return $data;
+ }
+
+ // 检查返回数据的序列化字段
+ protected function checkSerializeField(&$result)
+ {
+ // 检查序列化字段
+ if (!empty($this->serializeField)) {
+ foreach ($this->serializeField as $key => $val) {
+ if (isset($result[$key])) {
+ $serialize = unserialize($result[$key]);
+ foreach ($serialize as $name => $value) {
+ $result[$name] = $value;
+ }
+
+ unset($serialize, $result[$key]);
+ }
+ }
+ }
+ return $result;
+ }
+
+ // 检查数据集的序列化字段
+ protected function checkListSerializeField(&$resultSet)
+ {
+ // 检查序列化字段
+ if (!empty($this->serializeField)) {
+ foreach ($this->serializeField as $key => $val) {
+ foreach ($resultSet as $k => $result) {
+ if (isset($result[$key])) {
+ $serialize = unserialize($result[$key]);
+ foreach ($serialize as $name => $value) {
+ $result[$name] = $value;
+ }
+
+ unset($serialize, $result[$key]);
+ $resultSet[$k] = $result;
+ }
+ }
+ }
+ }
+ return $resultSet;
+ }
+
+ /**
+ * 检查只读字段
+ * @access protected
+ * @param array $data 数据
+ * @return array
+ */
+ protected function checkReadonlyField(&$data)
+ {
+ if (!empty($this->readonlyField)) {
+ foreach ($this->readonlyField as $key => $field) {
+ if (isset($data[$field])) {
+ unset($data[$field]);
+ }
+
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * 批处理执行SQL语句
+ * 批处理的指令都认为是execute操作
+ * @access public
+ * @param array $sql SQL批处理指令
+ * @return boolean
+ */
+ public function patchQuery($sql = array())
+ {
+ if (!is_array($sql)) {
+ return false;
+ }
+
+ // 自动启动事务支持
+ $this->startTrans();
+ try {
+ foreach ($sql as $_sql) {
+ $result = $this->execute($_sql);
+ if (false === $result) {
+ // 发生错误自动回滚事务
+ $this->rollback();
+ return false;
+ }
+ }
+ // 提交事务
+ $this->commit();
+ } catch (ThinkException $e) {
+ $this->rollback();
+ }
+ return true;
+ }
+
+ /**
+ * 得到分表的的数据表名
+ * @access public
+ * @param array $data 操作的数据
+ * @return string
+ */
+ public function getPartitionTableName($data = array())
+ {
+ // 对数据表进行分区
+ if (isset($data[$this->partition['field']])) {
+ $field = $data[$this->partition['field']];
+ switch ($this->partition['type']) {
+ case 'id':
+ // 按照id范围分表
+ $step = $this->partition['expr'];
+ $seq = floor($field / $step) + 1;
+ break;
+ case 'year':
+ // 按照年份分表
+ if (!is_numeric($field)) {
+ $field = strtotime($field);
+ }
+ $seq = date('Y', $field) - $this->partition['expr'] + 1;
+ break;
+ case 'mod':
+ // 按照id的模数分表
+ $seq = ($field % $this->partition['num']) + 1;
+ break;
+ case 'md5':
+ // 按照md5的序列分表
+ $seq = (ord(substr(md5($field), 0, 1)) % $this->partition['num']) + 1;
+ break;
+ default:
+ if (function_exists($this->partition['type'])) {
+ // 支持指定函数哈希
+ $fun = $this->partition['type'];
+ $seq = (ord(substr($fun($field), 0, 1)) % $this->partition['num']) + 1;
+ } else {
+ // 按照字段的首字母的值分表
+ $seq = (ord($field{0}) % $this->partition['num']) + 1;
+ }
+ }
+ return $this->getTableName() . '_' . $seq;
+ } else {
+ // 当设置的分表字段不在查询条件或者数据中
+ // 进行联合查询,必须设定 partition['num']
+ $tableName = array();
+ for ($i = 0; $i < $this->partition['num']; $i++) {
+ $tableName[] = 'SELECT * FROM ' . $this->getTableName() . '_' . ($i + 1);
+ }
+
+ $tableName = '( ' . implode(" UNION ", $tableName) . ') AS ' . $this->name;
+ return $tableName;
+ }
+ }
+}
diff --git a/Framework/Library/Think/Model/MergeModel.class.php b/Framework/Library/Think/Model/MergeModel.class.php
new file mode 100644
index 00000000..5b7ca2c7
--- /dev/null
+++ b/Framework/Library/Think/Model/MergeModel.class.php
@@ -0,0 +1,434 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Model;
+
+use Think\Model;
+
+/**
+ * ThinkPHP 聚合模型扩展
+ */
+class MergeModel extends Model
+{
+
+ protected $modelList = array(); // 包含的模型列表 第一个必须是主表模型
+ protected $masterModel = ''; // 主模型
+ protected $joinType = 'INNER'; // 聚合模型的查询JOIN类型
+ protected $fk = ''; // 外键名 默认为主表名_id
+ protected $mapFields = array(); // 需要处理的模型映射字段,避免混淆 array( id => 'user.id' )
+
+ /**
+ * 架构函数
+ * 取得DB类的实例对象 字段检查
+ * @access public
+ * @param string $name 模型名称
+ * @param string $tablePrefix 表前缀
+ * @param mixed $connection 数据库连接信息
+ */
+ public function __construct($name = '', $tablePrefix = '', $connection = '')
+ {
+ parent::__construct($name, $tablePrefix, $connection);
+ // 聚合模型的字段信息
+ if (empty($this->fields) && !empty($this->modelList)) {
+ $fields = array();
+ foreach ($this->modelList as $model) {
+ // 获取模型的字段信息
+ $result = $this->db->getFields(M($model)->getTableName());
+ $_fields = array_keys($result);
+ // $this->mapFields = array_intersect($fields,$_fields);
+ $fields = array_merge($fields, $_fields);
+ }
+ $this->fields = $fields;
+ }
+
+ // 设置第一个模型为主表模型
+ if (empty($this->masterModel) && !empty($this->modelList)) {
+ $this->masterModel = $this->modelList[0];
+ }
+ // 主表的主键名
+ $this->pk = M($this->masterModel)->getPk();
+
+ // 设置默认外键名 仅支持单一外键
+ if (empty($this->fk)) {
+ $this->fk = strtolower($this->masterModel) . '_id';
+ }
+
+ }
+
+ /**
+ * 得到完整的数据表名
+ * @access public
+ * @return string
+ */
+ public function getTableName()
+ {
+ if (empty($this->trueTableName)) {
+ $tableName = array();
+ $models = $this->modelList;
+ foreach ($models as $model) {
+ $tableName[] = M($model)->getTableName() . ' ' . $model;
+ }
+ $this->trueTableName = implode(',', $tableName);
+ }
+ return $this->trueTableName;
+ }
+
+ /**
+ * 自动检测数据表信息
+ * @access protected
+ * @return void
+ */
+ protected function _checkTableInfo()
+ {}
+
+ /**
+ * 新增聚合数据
+ * @access public
+ * @param mixed $data 数据
+ * @param array $options 表达式
+ * @param boolean $replace 是否replace
+ * @return mixed
+ */
+ public function add($data = '', $options = array(), $replace = false)
+ {
+ if (empty($data)) {
+ // 没有传递数据,获取当前数据对象的值
+ if (!empty($this->data)) {
+ $data = $this->data;
+ // 重置数据
+ $this->data = array();
+ } else {
+ $this->error = L('_DATA_TYPE_INVALID_');
+ return false;
+ }
+ }
+ // 启动事务
+ $this->startTrans();
+ // 写入主表数据
+ $result = M($this->masterModel)->strict(false)->add($data);
+ if ($result) {
+ // 写入外键数据
+ $data[$this->fk] = $result;
+ $models = $this->modelList;
+ array_shift($models);
+ // 写入附表数据
+ foreach ($models as $model) {
+ $res = M($model)->strict(false)->add($data);
+ if (!$res) {
+ $this->rollback();
+ return false;
+ }
+ }
+ // 提交事务
+ $this->commit();
+ } else {
+ $this->rollback();
+ return false;
+ }
+ return $result;
+ }
+
+ /**
+ * 对保存到数据库的数据进行处理
+ * @access protected
+ * @param mixed $data 要操作的数据
+ * @return boolean
+ */
+ protected function _facade($data)
+ {
+
+ // 检查数据字段合法性
+ if (!empty($this->fields)) {
+ if (!empty($this->options['field'])) {
+ $fields = $this->options['field'];
+ unset($this->options['field']);
+ if (is_string($fields)) {
+ $fields = explode(',', $fields);
+ }
+ } else {
+ $fields = $this->fields;
+ }
+ foreach ($data as $key => $val) {
+ if (!in_array($key, $fields, true)) {
+ unset($data[$key]);
+ } elseif (array_key_exists($key, $this->mapFields)) {
+ // 需要处理映射字段
+ $data[$this->mapFields[$key]] = $val;
+ unset($data[$key]);
+ }
+ }
+ }
+
+ // 安全过滤
+ if (!empty($this->options['filter'])) {
+ $data = array_map($this->options['filter'], $data);
+ unset($this->options['filter']);
+ }
+ $this->_before_write($data);
+ return $data;
+ }
+
+ /**
+ * 保存聚合模型数据
+ * @access public
+ * @param mixed $data 数据
+ * @param array $options 表达式
+ * @return boolean
+ */
+ public function save($data = '', $options = array())
+ {
+ // 根据主表的主键更新
+ if (empty($data)) {
+ // 没有传递数据,获取当前数据对象的值
+ if (!empty($this->data)) {
+ $data = $this->data;
+ // 重置数据
+ $this->data = array();
+ } else {
+ $this->error = L('_DATA_TYPE_INVALID_');
+ return false;
+ }
+ }
+ if (empty($data)) {
+ // 没有数据则不执行
+ $this->error = L('_DATA_TYPE_INVALID_');
+ return false;
+ }
+ // 如果存在主键数据 则自动作为更新条件
+ $pk = $this->pk;
+ if (isset($data[$pk])) {
+ $where[$pk] = $data[$pk];
+ $options['where'] = $where;
+ unset($data[$pk]);
+ }
+ $options['join'] = '';
+ $options = $this->_parseOptions($options);
+ // 更新操作不使用JOIN
+ $options['table'] = $this->getTableName();
+
+ if (is_array($options['where']) && isset($options['where'][$pk])) {
+ $pkValue = $options['where'][$pk];
+ }
+ if (false === $this->_before_update($data, $options)) {
+ return false;
+ }
+ $result = $this->db->update($data, $options);
+ if (false !== $result) {
+ if (isset($pkValue)) {
+ $data[$pk] = $pkValue;
+ }
+
+ $this->_after_update($data, $options);
+ }
+ return $result;
+ }
+
+ /**
+ * 删除聚合模型数据
+ * @access public
+ * @param mixed $options 表达式
+ * @return mixed
+ */
+ public function delete($options = array())
+ {
+ $pk = $this->pk;
+ if (empty($options) && empty($this->options['where'])) {
+ // 如果删除条件为空 则删除当前数据对象所对应的记录
+ if (!empty($this->data) && isset($this->data[$pk])) {
+ return $this->delete($this->data[$pk]);
+ } else {
+ return false;
+ }
+
+ }
+
+ if (is_numeric($options) || is_string($options)) {
+ // 根据主键删除记录
+ if (strpos($options, ',')) {
+ $where[$pk] = array('IN', $options);
+ } else {
+ $where[$pk] = $options;
+ }
+ $options = array();
+ $options['where'] = $where;
+ }
+ // 分析表达式
+ $options['join'] = '';
+ $options = $this->_parseOptions($options);
+ if (empty($options['where'])) {
+ // 如果条件为空 不进行删除操作 除非设置 1=1
+ return false;
+ }
+ if (is_array($options['where']) && isset($options['where'][$pk])) {
+ $pkValue = $options['where'][$pk];
+ }
+
+ $options['table'] = implode(',', $this->modelList);
+ $options['using'] = $this->getTableName();
+ if (false === $this->_before_delete($options)) {
+ return false;
+ }
+ $result = $this->db->delete($options);
+ if (false !== $result) {
+ $data = array();
+ if (isset($pkValue)) {
+ $data[$pk] = $pkValue;
+ }
+
+ $this->_after_delete($data, $options);
+ }
+ // 返回删除记录个数
+ return $result;
+ }
+
+ /**
+ * 表达式过滤方法
+ * @access protected
+ * @param string $options 表达式
+ * @return void
+ */
+ protected function _options_filter(&$options)
+ {
+ if (!isset($options['join'])) {
+ $models = $this->modelList;
+ array_shift($models);
+ foreach ($models as $model) {
+ $options['join'][] = $this->joinType . ' JOIN ' . M($model)->getTableName() . ' ' . $model . ' ON ' . $this->masterModel . '.' . $this->pk . ' = ' . $model . '.' . $this->fk;
+ }
+ }
+ $options['table'] = M($this->masterModel)->getTableName() . ' ' . $this->masterModel;
+ $options['field'] = $this->checkFields(isset($options['field']) ? $options['field'] : '');
+ if (isset($options['group'])) {
+ $options['group'] = $this->checkGroup($options['group']);
+ }
+
+ if (isset($options['where'])) {
+ $options['where'] = $this->checkCondition($options['where']);
+ }
+
+ if (isset($options['order'])) {
+ $options['order'] = $this->checkOrder($options['order']);
+ }
+
+ }
+
+ /**
+ * 检查条件中的聚合字段
+ * @access protected
+ * @param mixed $data 条件表达式
+ * @return array
+ */
+ protected function checkCondition($where)
+ {
+ if (is_array($where)) {
+ $view = array();
+ foreach ($where as $name => $value) {
+ if (array_key_exists($name, $this->mapFields)) {
+ // 需要处理映射字段
+ $view[$this->mapFields[$name]] = $value;
+ unset($where[$name]);
+ }
+ }
+ $where = array_merge($where, $view);
+ }
+ return $where;
+ }
+
+ /**
+ * 检查Order表达式中的聚合字段
+ * @access protected
+ * @param string $order 字段
+ * @return string
+ */
+ protected function checkOrder($order = '')
+ {
+ if (is_string($order) && !empty($order)) {
+ $orders = explode(',', $order);
+ $_order = array();
+ foreach ($orders as $order) {
+ $array = explode(' ', trim($order));
+ $field = $array[0];
+ $sort = isset($array[1]) ? $array[1] : 'ASC';
+ if (array_key_exists($field, $this->mapFields)) {
+ // 需要处理映射字段
+ $field = $this->mapFields[$field];
+ }
+ $_order[] = $field . ' ' . $sort;
+ }
+ $order = implode(',', $_order);
+ }
+ return $order;
+ }
+
+ /**
+ * 检查Group表达式中的聚合字段
+ * @access protected
+ * @param string $group 字段
+ * @return string
+ */
+ protected function checkGroup($group = '')
+ {
+ if (!empty($group)) {
+ $groups = explode(',', $group);
+ $_group = array();
+ foreach ($groups as $field) {
+ // 解析成聚合字段
+ if (array_key_exists($field, $this->mapFields)) {
+ // 需要处理映射字段
+ $field = $this->mapFields[$field];
+ }
+ $_group[] = $field;
+ }
+ $group = implode(',', $_group);
+ }
+ return $group;
+ }
+
+ /**
+ * 检查fields表达式中的聚合字段
+ * @access protected
+ * @param string $fields 字段
+ * @return string
+ */
+ protected function checkFields($fields = '')
+ {
+ if (empty($fields) || '*' == $fields) {
+ // 获取全部聚合字段
+ $fields = $this->fields;
+ }
+ if (!is_array($fields)) {
+ $fields = explode(',', $fields);
+ }
+
+ // 解析成聚合字段
+ $array = array();
+ foreach ($fields as $field) {
+ if (array_key_exists($field, $this->mapFields)) {
+ // 需要处理映射字段
+ $array[] = $this->mapFields[$field] . ' AS ' . $field;
+ } else {
+ $array[] = $field;
+ }
+ }
+ $fields = implode(',', $array);
+ return $fields;
+ }
+
+ /**
+ * 获取数据表字段信息
+ * @access public
+ * @return array
+ */
+ public function getDbFields()
+ {
+ return $this->fields;
+ }
+
+}
diff --git a/Framework/Library/Think/Model/MongoModel.class.php b/Framework/Library/Think/Model/MongoModel.class.php
new file mode 100644
index 00000000..1c3b3b4a
--- /dev/null
+++ b/Framework/Library/Think/Model/MongoModel.class.php
@@ -0,0 +1,499 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Model;
+
+use Think\Model;
+
+/**
+ * MongoModel模型类
+ * 实现了ODM和ActiveRecords模式
+ */
+class MongoModel extends Model
+{
+ // 主键类型
+ const TYPE_OBJECT = 1;
+ const TYPE_INT = 2;
+ const TYPE_STRING = 3;
+
+ // 主键名称
+ protected $pk = '_id';
+ // _id 类型 1 Object 采用MongoId对象 2 Int 整形 支持自动增长 3 String 字符串Hash
+ protected $_idType = self::TYPE_OBJECT;
+ // 主键是否自增
+ protected $_autoinc = true;
+ // Mongo默认关闭字段检测 可以动态追加字段
+ protected $autoCheckFields = false;
+ // 链操作方法列表
+ protected $methods = array('table', 'order', 'auto', 'filter', 'validate');
+
+ /**
+ * 利用__call方法实现一些特殊的Model方法
+ * @access public
+ * @param string $method 方法名称
+ * @param array $args 调用参数
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ if (in_array(strtolower($method), $this->methods, true)) {
+ // 连贯操作的实现
+ $this->options[strtolower($method)] = $args[0];
+ return $this;
+ } elseif (strtolower(substr($method, 0, 5)) == 'getby') {
+ // 根据某个字段获取记录
+ $field = parse_name(substr($method, 5));
+ $where[$field] = $args[0];
+ return $this->where($where)->find();
+ } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
+ // 根据某个字段获取记录的某个值
+ $name = parse_name(substr($method, 10));
+ $where[$name] = $args[0];
+ return $this->where($where)->getField($args[1]);
+ } else {
+ E(__CLASS__ . ':' . $method . L('_METHOD_NOT_EXIST_'));
+ return;
+ }
+ }
+
+ /**
+ * 获取字段信息并缓存 主键和自增信息直接配置
+ * @access public
+ * @return void
+ */
+ public function flush()
+ {
+ // 缓存不存在则查询数据表信息
+ $fields = $this->db->getFields();
+ if (!$fields) {
+ // 暂时没有数据无法获取字段信息 下次查询
+ return false;
+ }
+ $this->fields = array_keys($fields);
+ foreach ($fields as $key => $val) {
+ // 记录字段类型
+ $type[$key] = $val['type'];
+ }
+ // 记录字段类型信息
+ if (C('DB_FIELDTYPE_CHECK')) {
+ $this->fields['_type'] = $type;
+ }
+
+ // 2008-3-7 增加缓存开关控制
+ if (C('DB_FIELDS_CACHE')) {
+ // 永久缓存数据表信息
+ $db = $this->dbName ? $this->dbName : C('DB_NAME');
+ F('_fields/' . $db . '.' . $this->name, $this->fields);
+ }
+ }
+
+ // 写入数据前的回调方法 包括新增和更新
+ protected function _before_write(&$data)
+ {
+ $pk = $this->getPk();
+ // 根据主键类型处理主键数据
+ if (isset($data[$pk]) && self::TYPE_OBJECT == $this->_idType) {
+ $data[$pk] = new \MongoId($data[$pk]);
+ }
+ }
+
+ /**
+ * count统计 配合where连贯操作
+ * @access public
+ * @return integer
+ */
+ public function count()
+ {
+ // 分析表达式
+ $options = $this->_parseOptions();
+ return $this->db->count($options);
+ }
+
+ /**
+ * 获取唯一值
+ * @access public
+ * @return array | false
+ */
+ public function distinct($field, $where = array())
+ {
+ // 分析表达式
+ $this->options = $this->_parseOptions();
+ $this->options['where'] = array_merge((array) $this->options['where'], $where);
+
+ $command = array(
+ "distinct" => $this->options['table'],
+ "key" => $field,
+ "query" => $this->options['where'],
+ );
+
+ $result = $this->command($command);
+ return isset($result['values']) ? $result['values'] : false;
+ }
+
+ /**
+ * 获取下一ID 用于自动增长型
+ * @access public
+ * @param string $pk 字段名 默认为主键
+ * @return mixed
+ */
+ public function getMongoNextId($pk = '')
+ {
+ if (empty($pk)) {
+ $pk = $this->getPk();
+ }
+ $options = $this->_parseOptions();
+ return $this->db->getMongoNextId($pk,$options);
+ }
+
+ /**
+ * 新增数据
+ * @access public
+ * @param mixed $data 数据
+ * @param array $options 表达式
+ * @param boolean $replace 是否replace
+ * @return mixed
+ */
+ public function add($data = '', $options = array(), $replace = false)
+ {
+ if (empty($data)) {
+ // 没有传递数据,获取当前数据对象的值
+ if (!empty($this->data)) {
+ $data = $this->data;
+ // 重置数据
+ $this->data = array();
+ } else {
+ $this->error = L('_DATA_TYPE_INVALID_');
+ return false;
+ }
+ }
+ // 分析表达式
+ $options = $this->_parseOptions($options);
+ // 数据处理
+ $data = $this->_facade($data);
+ if (false === $this->_before_insert($data, $options)) {
+ return false;
+ }
+ // 写入数据到数据库
+ $result = $this->db->insert($data, $options, $replace);
+ if (false !== $result) {
+ $this->_after_insert($data, $options);
+ if (isset($data[$this->getPk()])) {
+ return $data[$this->getPk()];
+ }
+ }
+ return $result;
+ }
+
+ // 插入数据前的回调方法
+ protected function _before_insert(&$data, $options)
+ {
+ // 写入数据到数据库
+ if ($this->_autoinc && self::TYPE_INT == $this->_idType) {
+ // 主键自动增长
+ $pk = $this->getPk();
+ if (!isset($data[$pk])) {
+ $data[$pk] = $this->db->getMongoNextId($pk, $options);
+ }
+ }
+ }
+
+ public function clear()
+ {
+ return $this->db->clear();
+ }
+
+ // 查询成功后的回调方法
+ protected function _after_select(&$resultSet, $options)
+ {
+ array_walk($resultSet, array($this, 'checkMongoId'));
+ }
+
+ /**
+ * 获取MongoId
+ * @access protected
+ * @param array $result 返回数据
+ * @return array
+ */
+ protected function checkMongoId(&$result)
+ {
+ if (is_object($result['_id'])) {
+ $result['_id'] = $result['_id']->__toString();
+ }
+ return $result;
+ }
+
+ // 表达式过滤回调方法
+ protected function _options_filter(&$options)
+ {
+ $id = $this->getPk();
+ if (isset($options['where'][$id]) && is_scalar($options['where'][$id]) && self::TYPE_OBJECT == $this->_idType) {
+ $options['where'][$id] = new \MongoId($options['where'][$id]);
+ }
+ }
+
+ /**
+ * 查询多行数据
+ * @access public
+ * @param mixed $options 表达式参数
+ * @return mixed
+ */
+ public function select($options = array())
+ {
+ if( is_numeric($options) || is_string($options)) {
+ $id = $this->getPk();
+ $where[$id] = $options;
+ $options = array();
+ $options['where'] = $where;
+ }
+ // 分析表达式
+ $options = $this->_parseOptions($options);
+ $result = $this->db->select($options);
+ if(false === $result) {
+ return false;
+ }
+
+ if(empty($result)) {// 查询结果为空
+ return null;
+ }
+ else{
+ $this->checkMongoId($result);
+ }
+
+ //$result是以主键为key的,所以需要处理一下
+ $data = array();
+ foreach($result as $v){
+ $data[] = $v;
+ }
+
+ $this->data = $data;
+ $this->_after_select($this->data, $options);
+
+ return $this->data;
+ }
+
+ /**
+ * 查询数据
+ * @access public
+ * @param mixed $options 表达式参数
+ * @return mixed
+ */
+ public function find($options = array())
+ {
+ if (is_numeric($options) || is_string($options)) {
+ $id = $this->getPk();
+ $where[$id] = $options;
+ $options = array();
+ $options['where'] = $where;
+ }
+ // 分析表达式
+ $options = $this->_parseOptions($options);
+ $result = $this->db->find($options);
+ if (false === $result) {
+ return false;
+ }
+ if (empty($result)) {
+ // 查询结果为空
+ return null;
+ } else {
+ $this->checkMongoId($result);
+ }
+ $this->data = $result;
+ $this->_after_find($this->data, $options);
+ return $this->data;
+ }
+
+ /**
+ * 字段值增长
+ * @access public
+ * @param string $field 字段名
+ * @param integer $step 增长值
+ * @return boolean
+ */
+ public function setInc($field, $step = 1)
+ {
+ return $this->setField($field, array('inc', $step));
+ }
+
+ /**
+ * 字段值减少
+ * @access public
+ * @param string $field 字段名
+ * @param integer $step 减少值
+ * @return boolean
+ */
+ public function setDec($field, $step = 1)
+ {
+ return $this->setField($field, array('inc', '-' . $step));
+ }
+
+ /**
+ * 获取一条记录的某个字段值
+ * @access public
+ * @param string $field 字段名
+ * @param string $spea 字段数据间隔符号
+ * @return mixed
+ */
+ public function getField($field, $sepa = null)
+ {
+ $options['field'] = $field;
+ $options = $this->_parseOptions($options);
+ if (strpos($field, ',')) {
+ // 多字段
+ if (is_numeric($sepa)) { // 限定数量
+ $options['limit'] = $sepa;
+ $sepa = null; // 重置为null 返回数组
+ }
+ $resultSet = $this->db->select($options);
+ if (!empty($resultSet)) {
+ $_field = explode(',', $field);
+ $field = array_keys($resultSet[0]);
+ $key = array_shift($field);
+ $key2 = array_shift($field);
+ $cols = array();
+ $count = count($_field);
+ foreach ($resultSet as $result) {
+ $name = $result[$key];
+ if (2 == $count) {
+ $cols[$name] = $result[$key2];
+ } else {
+ $cols[$name] = is_null($sepa) ? $result : implode($sepa, $result);
+ }
+ }
+ return $cols;
+ }
+ } else {
+ // 返回数据个数
+ if (true !== $sepa) {
+ // 当sepa指定为true的时候 返回所有数据
+ $options['limit'] = is_numeric($sepa) ? $sepa : 1;
+ } // 查找符合的记录
+ $result = $this->db->select($options);
+ if (!empty($result)) {
+ if (1 == $options['limit']) {
+ $result = reset($result);
+ return $result[$field];
+ }
+ foreach ($result as $val) {
+ $array[] = $val[$field];
+ }
+ return $array;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 执行Mongo指令
+ * @access public
+ * @param array $command 指令
+ * @return mixed
+ */
+ public function command($command, $options = array())
+ {
+ $options = $this->_parseOptions($options);
+ return $this->db->command($command, $options);
+ }
+
+ /**
+ * 执行MongoCode
+ * @access public
+ * @param string $code MongoCode
+ * @param array $args 参数
+ * @return mixed
+ */
+ public function mongoCode($code, $args = array())
+ {
+ return $this->db->execute($code, $args);
+ }
+
+ // 数据库切换后回调方法
+ protected function _after_db()
+ {
+ // 切换Collection
+ $this->db->switchCollection($this->getTableName(), $this->dbName ? $this->dbName : C('db_name'));
+ }
+
+ /**
+ * 得到完整的数据表名 Mongo表名不带dbName
+ * @access public
+ * @return string
+ */
+ public function getTableName()
+ {
+ if (empty($this->trueTableName)) {
+ $tableName = !empty($this->tablePrefix) ? $this->tablePrefix : '';
+ if (!empty($this->tableName)) {
+ $tableName .= $this->tableName;
+ } else {
+ $tableName .= parse_name($this->name);
+ }
+ $this->trueTableName = strtolower($tableName);
+ }
+ return $this->trueTableName;
+ }
+
+ /**
+ * 分组查询
+ * @access public
+ * @return string
+ */
+ public function group($key, $init, $reduce, $option = array())
+ {
+ $option = $this->_parseOptions($option);
+
+ //合并查询条件
+ if (isset($option['where'])) {
+ $option['condition'] = array_merge((array) $option['condition'], $option['where']);
+ }
+
+ return $this->db->group($key, $init, $reduce, $option);
+ }
+
+ /**
+ * 返回Mongo运行错误信息
+ * @access public
+ * @return json
+ */
+ public function getLastError()
+ {
+ return $this->db->command(array('getLastError' => 1));
+ }
+
+ /**
+ * 返回指定集合的统计信息,包括数据大小、已分配的存储空间和索引的大小
+ * @access public
+ * @return json
+ */
+ public function status()
+ {
+ $option = $this->_parseOptions();
+ return $this->db->command(array('collStats' => $option['table']));
+ }
+
+ /**
+ * 取得当前数据库的对象
+ * @access public
+ * @return object
+ */
+ public function getDB()
+ {
+ return $this->db->getDB();
+ }
+
+ /**
+ * 取得集合对象,可以进行创建索引等查询
+ * @access public
+ * @return object
+ */
+ public function getCollection()
+ {
+ return $this->db->getCollection();
+ }
+}
diff --git a/Framework/Library/Think/Model/RelationModel.class.php b/Framework/Library/Think/Model/RelationModel.class.php
new file mode 100644
index 00000000..dd332a84
--- /dev/null
+++ b/Framework/Library/Think/Model/RelationModel.class.php
@@ -0,0 +1,452 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Model;
+
+use Think\Model;
+
+/**
+ * ThinkPHP关联模型扩展
+ */
+class RelationModel extends Model
+{
+
+ const HAS_ONE = 1;
+ const BELONGS_TO = 2;
+ const HAS_MANY = 3;
+ const MANY_TO_MANY = 4;
+
+ // 关联定义
+ protected $_link = array();
+
+ /**
+ * 动态方法实现
+ * @access public
+ * @param string $method 方法名称
+ * @param array $args 调用参数
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ if (strtolower(substr($method, 0, 8)) == 'relation') {
+ $type = strtoupper(substr($method, 8));
+ if (in_array($type, array('ADD', 'SAVE', 'DEL'), true)) {
+ array_unshift($args, $type);
+ return call_user_func_array(array(&$this, 'opRelation'), $args);
+ }
+ } else {
+ return parent::__call($method, $args);
+ }
+ }
+
+ /**
+ * 得到关联的数据表名
+ * @access public
+ * @return string
+ */
+ public function getRelationTableName($relation)
+ {
+ $relationTable = !empty($this->tablePrefix) ? $this->tablePrefix : '';
+ $relationTable .= $this->tableName ? $this->tableName : $this->name;
+ $relationTable .= '_' . $relation->getModelName();
+ return strtolower($relationTable);
+ }
+
+ // 查询成功后的回调方法
+ protected function _after_find(&$result, $options)
+ {
+ // 获取关联数据 并附加到结果中
+ if (!empty($options['link'])) {
+ $this->getRelation($result, $options['link']);
+ }
+
+ }
+
+ // 查询数据集成功后的回调方法
+ protected function _after_select(&$result, $options)
+ {
+ // 获取关联数据 并附加到结果中
+ if (!empty($options['link'])) {
+ $this->getRelations($result, $options['link']);
+ }
+
+ }
+
+ // 写入成功后的回调方法
+ protected function _after_insert($data, $options)
+ {
+ // 关联写入
+ if (!empty($options['link'])) {
+ $this->opRelation('ADD', $data, $options['link']);
+ }
+
+ }
+
+ // 更新成功后的回调方法
+ protected function _after_update($data, $options)
+ {
+ // 关联更新
+ if (!empty($options['link'])) {
+ $this->opRelation('SAVE', $data, $options['link']);
+ }
+
+ }
+
+ // 删除成功后的回调方法
+ protected function _after_delete($data, $options)
+ {
+ // 关联删除
+ if (!empty($options['link'])) {
+ $this->opRelation('DEL', $data, $options['link']);
+ }
+
+ }
+
+ /**
+ * 对保存到数据库的数据进行处理
+ * @access protected
+ * @param mixed $data 要操作的数据
+ * @return boolean
+ */
+ protected function _facade($data)
+ {
+ $this->_before_write($data);
+ return $data;
+ }
+
+ /**
+ * 获取返回数据集的关联记录
+ * @access protected
+ * @param array $resultSet 返回数据
+ * @param string|array $name 关联名称
+ * @return array
+ */
+ protected function getRelations(&$resultSet, $name = '')
+ {
+ // 获取记录集的主键列表
+ foreach ($resultSet as $key => $val) {
+ $val = $this->getRelation($val, $name);
+ $resultSet[$key] = $val;
+ }
+ return $resultSet;
+ }
+
+ /**
+ * 获取返回数据的关联记录
+ * @access protected
+ * @param mixed $result 返回数据
+ * @param string|array $name 关联名称
+ * @param boolean $return 是否返回关联数据本身
+ * @return array
+ */
+ protected function getRelation(&$result, $name = '', $return = false)
+ {
+ if (!empty($this->_link)) {
+ foreach ($this->_link as $key => $val) {
+ $mappingName = !empty($val['mapping_name']) ? $val['mapping_name'] : $key; // 映射名称
+ if (empty($name) || true === $name || $mappingName == $name || (is_array($name) && in_array($mappingName, $name))) {
+ $mappingType = !empty($val['mapping_type']) ? $val['mapping_type'] : $val; // 关联类型
+ $mappingClass = !empty($val['class_name']) ? $val['class_name'] : $key; // 关联类名
+ $mappingFields = !empty($val['mapping_fields']) ? $val['mapping_fields'] : '*'; // 映射字段
+ $mappingCondition = !empty($val['condition']) ? $val['condition'] : '1=1'; // 关联条件
+ $mappingKey = !empty($val['mapping_key']) ? $val['mapping_key'] : $this->getPk(); // 关联键名
+ if (strtoupper($mappingClass) == strtoupper($this->name)) {
+ // 自引用关联 获取父键名
+ $mappingFk = !empty($val['parent_key']) ? $val['parent_key'] : 'parent_id';
+ } else {
+ $mappingFk = !empty($val['foreign_key']) ? $val['foreign_key'] : strtolower($this->name) . '_id'; // 关联外键
+ }
+ // 获取关联模型对象
+ $model = D($mappingClass);
+ switch ($mappingType) {
+ case self::HAS_ONE:
+ $pk = $result[$mappingKey];
+ $mappingCondition .= " AND {$mappingFk}='{$pk}'";
+ $relationData = $model->where($mappingCondition)->field($mappingFields)->find();
+ if (!empty($val['relation_deep'])) {
+ $model->getRelation($relationData, $val['relation_deep']);
+ }
+ break;
+ case self::BELONGS_TO:
+ if (strtoupper($mappingClass) == strtoupper($this->name)) {
+ // 自引用关联 获取父键名
+ $mappingFk = !empty($val['parent_key']) ? $val['parent_key'] : 'parent_id';
+ } else {
+ $mappingFk = !empty($val['foreign_key']) ? $val['foreign_key'] : strtolower($model->getModelName()) . '_id'; // 关联外键
+ }
+ $fk = $result[$mappingFk];
+ $mappingCondition .= " AND {$model->getPk()}='{$fk}'";
+ $relationData = $model->where($mappingCondition)->field($mappingFields)->find();
+ if (!empty($val['relation_deep'])) {
+ $model->getRelation($relationData, $val['relation_deep']);
+ }
+ break;
+ case self::HAS_MANY:
+ $pk = $result[$mappingKey];
+ $mappingCondition .= " AND {$mappingFk}='{$pk}'";
+ $mappingOrder = !empty($val['mapping_order']) ? $val['mapping_order'] : '';
+ $mappingLimit = !empty($val['mapping_limit']) ? $val['mapping_limit'] : '';
+ // 延时获取关联记录
+ $relationData = $model->where($mappingCondition)->field($mappingFields)->order($mappingOrder)->limit($mappingLimit)->select();
+ if (!empty($val['relation_deep'])) {
+ foreach ($relationData as $key => $data) {
+ $model->getRelation($data, $val['relation_deep']);
+ $relationData[$key] = $data;
+ }
+ }
+ break;
+ case self::MANY_TO_MANY:
+ $pk = $result[$mappingKey];
+ $prefix = $this->tablePrefix;
+ $mappingCondition = " {$mappingFk}='{$pk}'";
+ $mappingOrder = $val['mapping_order'];
+ $mappingLimit = $val['mapping_limit'];
+ $mappingRelationFk = $val['relation_foreign_key'] ? $val['relation_foreign_key'] : $model->getModelName() . '_id';
+ if (isset($val['relation_table'])) {
+ $mappingRelationTable = preg_replace_callback("/__([A-Z_-]+)__/sU", function ($match) use ($prefix) {return $prefix . strtolower($match[1]);}, $val['relation_table']);
+ } else {
+ $mappingRelationTable = $this->getRelationTableName($model);
+ }
+ $sql = "SELECT b.{$mappingFields} FROM {$mappingRelationTable} AS a, " . $model->getTableName() . " AS b WHERE a.{$mappingRelationFk} = b.{$model->getPk()} AND a.{$mappingCondition}";
+ if (!empty($val['condition'])) {
+ $sql .= ' AND ' . $val['condition'];
+ }
+ if (!empty($mappingOrder)) {
+ $sql .= ' ORDER BY ' . $mappingOrder;
+ }
+ if (!empty($mappingLimit)) {
+ $sql .= ' LIMIT ' . $mappingLimit;
+ }
+ $relationData = $this->query($sql);
+ if (!empty($val['relation_deep'])) {
+ foreach ($relationData as $key => $data) {
+ $model->getRelation($data, $val['relation_deep']);
+ $relationData[$key] = $data;
+ }
+ }
+ break;
+ }
+ if (!$return) {
+ if (isset($val['as_fields']) && in_array($mappingType, array(self::HAS_ONE, self::BELONGS_TO))) {
+ // 支持直接把关联的字段值映射成数据对象中的某个字段
+ // 仅仅支持HAS_ONE BELONGS_TO
+ $fields = explode(',', $val['as_fields']);
+ foreach ($fields as $field) {
+ if (strpos($field, ':')) {
+ list($relationName, $nick) = explode(':', $field);
+ $result[$nick] = $relationData[$relationName];
+ } else {
+ $result[$field] = $relationData[$field];
+ }
+ }
+ } else {
+ $result[$mappingName] = $relationData;
+ }
+ unset($relationData);
+ } else {
+ return $relationData;
+ }
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * 操作关联数据
+ * @access protected
+ * @param string $opType 操作方式 ADD SAVE DEL
+ * @param mixed $data 数据对象
+ * @param string $name 关联名称
+ * @return mixed
+ */
+ protected function opRelation($opType, $data = '', $name = '')
+ {
+ $result = false;
+ if (empty($data) && !empty($this->data)) {
+ $data = $this->data;
+ } elseif (!is_array($data)) {
+ // 数据无效返回
+ return false;
+ }
+ if (!empty($this->_link)) {
+ // 遍历关联定义
+ foreach ($this->_link as $key => $val) {
+ // 操作制定关联类型
+ $mappingName = $val['mapping_name'] ? $val['mapping_name'] : $key; // 映射名称
+ if (empty($name) || true === $name || $mappingName == $name || (is_array($name) && in_array($mappingName, $name))) {
+ // 操作制定的关联
+ $mappingType = !empty($val['mapping_type']) ? $val['mapping_type'] : $val; // 关联类型
+ $mappingClass = !empty($val['class_name']) ? $val['class_name'] : $key; // 关联类名
+ $mappingKey = !empty($val['mapping_key']) ? $val['mapping_key'] : $this->getPk(); // 关联键名
+ // 当前数据对象主键值
+ $pk = $data[$mappingKey];
+ if (strtoupper($mappingClass) == strtoupper($this->name)) {
+ // 自引用关联 获取父键名
+ $mappingFk = !empty($val['parent_key']) ? $val['parent_key'] : 'parent_id';
+ } else {
+ $mappingFk = !empty($val['foreign_key']) ? $val['foreign_key'] : strtolower($this->name) . '_id'; // 关联外键
+ }
+ if (!empty($val['condition'])) {
+ $mappingCondition = $val['condition'];
+ } else {
+ $mappingCondition = array();
+ $mappingCondition[$mappingFk] = $pk;
+ }
+ // 获取关联model对象
+ $model = D($mappingClass);
+ $mappingData = isset($data[$mappingName]) ? $data[$mappingName] : false;
+ if (!empty($mappingData) || 'DEL' == $opType) {
+ switch ($mappingType) {
+ case self::HAS_ONE:
+ switch (strtoupper($opType)) {
+ case 'ADD': // 增加关联数据
+ $mappingData[$mappingFk] = $pk;
+ $result = $model->add($mappingData);
+ break;
+ case 'SAVE': // 更新关联数据
+ $result = $model->where($mappingCondition)->save($mappingData);
+ break;
+ case 'DEL': // 根据外键删除关联数据
+ $result = $model->where($mappingCondition)->delete();
+ break;
+ }
+ break;
+ case self::BELONGS_TO:
+ break;
+ case self::HAS_MANY:
+ switch (strtoupper($opType)) {
+ case 'ADD': // 增加关联数据
+ $model->startTrans();
+ foreach ($mappingData as $val) {
+ $val[$mappingFk] = $pk;
+ $result = $model->add($val);
+ }
+ $model->commit();
+ break;
+ case 'SAVE': // 更新关联数据
+ $model->startTrans();
+ $pk = $model->getPk();
+ foreach ($mappingData as $vo) {
+ if (isset($vo[$pk])) {
+// 更新数据
+ $mappingCondition = "$pk ={$vo[$pk]}";
+ $result = $model->where($mappingCondition)->save($vo);
+ } else {
+ // 新增数据
+ $vo[$mappingFk] = $data[$mappingKey];
+ $result = $model->add($vo);
+ }
+ }
+ $model->commit();
+ break;
+ case 'DEL': // 删除关联数据
+ $result = $model->where($mappingCondition)->delete();
+ break;
+ }
+ break;
+ case self::MANY_TO_MANY:
+ $mappingRelationFk = $val['relation_foreign_key'] ? $val['relation_foreign_key'] : $model->getModelName() . '_id'; // 关联
+ $prefix = $this->tablePrefix;
+ if (isset($val['relation_table'])) {
+ $mappingRelationTable = preg_replace_callback("/__([A-Z_-]+)__/sU", function ($match) use ($prefix) {return $prefix . strtolower($match[1]);}, $val['relation_table']);
+ } else {
+ $mappingRelationTable = $this->getRelationTableName($model);
+ }
+ if (is_array($mappingData)) {
+ $ids = array();
+ foreach ($mappingData as $vo) {
+ $ids[] = $vo[$mappingKey];
+ }
+
+ $relationId = implode(',', $ids);
+ }
+ switch (strtoupper($opType)) {
+ case 'ADD': // 增加关联数据
+ if (isset($relationId)) {
+ $this->startTrans();
+ // 插入关联表数据
+ $sql = 'INSERT INTO ' . $mappingRelationTable . ' (' . $mappingFk . ',' . $mappingRelationFk . ') SELECT a.' . $this->getPk() . ',b.' . $model->getPk() . ' FROM ' . $this->getTableName() . ' AS a ,' . $model->getTableName() . " AS b where a." . $this->getPk() . ' =' . $pk . ' AND b.' . $model->getPk() . ' IN (' . $relationId . ") ";
+ $result = $model->execute($sql);
+ if (false !== $result)
+ // 提交事务
+ {
+ $this->commit();
+ } else
+ // 事务回滚
+ {
+ $this->rollback();
+ }
+
+ }
+ break;
+ case 'SAVE': // 更新关联数据
+ if (isset($relationId)) {
+ $this->startTrans();
+ // 删除关联表数据
+ $this->table($mappingRelationTable)->where($mappingCondition)->delete();
+ // 插入关联表数据
+ $sql = 'INSERT INTO ' . $mappingRelationTable . ' (' . $mappingFk . ',' . $mappingRelationFk . ') SELECT a.' . $this->getPk() . ',b.' . $model->getPk() . ' FROM ' . $this->getTableName() . ' AS a ,' . $model->getTableName() . " AS b where a." . $this->getPk() . ' =' . $pk . ' AND b.' . $model->getPk() . ' IN (' . $relationId . ") ";
+ $result = $model->execute($sql);
+ if (false !== $result)
+ // 提交事务
+ {
+ $this->commit();
+ } else
+ // 事务回滚
+ {
+ $this->rollback();
+ }
+
+ }
+ break;
+ case 'DEL': // 根据外键删除中间表关联数据
+ $result = $this->table($mappingRelationTable)->where($mappingCondition)->delete();
+ break;
+ }
+ break;
+ }
+ if (!empty($val['relation_deep'])) {
+ $model->opRelation($opType, $mappingData, $val['relation_deep']);
+ }
+ }
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * 进行关联查询
+ * @access public
+ * @param mixed $name 关联名称
+ * @return Model
+ */
+ public function relation($name)
+ {
+ $this->options['link'] = $name;
+ return $this;
+ }
+
+ /**
+ * 关联数据获取 仅用于查询后
+ * @access public
+ * @param string $name 关联名称
+ * @return array
+ */
+ public function relationGet($name)
+ {
+ if (empty($this->data)) {
+ return false;
+ }
+
+ return $this->getRelation($this->data, $name, true);
+ }
+}
diff --git a/Framework/Library/Think/Model/ViewModel.class.php b/Framework/Library/Think/Model/ViewModel.class.php
new file mode 100644
index 00000000..a91d27e4
--- /dev/null
+++ b/Framework/Library/Think/Model/ViewModel.class.php
@@ -0,0 +1,324 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Model;
+
+use Think\Model;
+
+/**
+ * ThinkPHP视图模型扩展
+ */
+class ViewModel extends Model
+{
+
+ protected $viewFields = array();
+
+ /**
+ * 自动检测数据表信息
+ * @access protected
+ * @return void
+ */
+ protected function _checkTableInfo()
+ {}
+
+ /**
+ * 得到完整的数据表名
+ * @access public
+ * @return string
+ */
+ public function getTableName()
+ {
+ if (empty($this->trueTableName)) {
+ $tableName = '';
+ foreach ($this->viewFields as $key => $view) {
+ // 获取数据表名称
+ if (isset($view['_table'])) {
+ // 2011/10/17 添加实际表名定义支持 可以实现同一个表的视图
+ $tableName .= $view['_table'];
+ $prefix = $this->tablePrefix;
+ $tableName = preg_replace_callback("/__([A-Z_-]+)__/sU", function ($match) use ($prefix) {return $prefix . strtolower($match[1]);}, $tableName);
+ } else {
+ $class = parse_res_name($key, C('DEFAULT_M_LAYER'));
+ $Model = class_exists($class) ? new $class() : M($key);
+ $tableName .= $Model->getTableName();
+ }
+ // 表别名定义
+ $tableName .= !empty($view['_as']) ? ' ' . $view['_as'] : ' ' . $key;
+ // 支持ON 条件定义
+ $tableName .= !empty($view['_on']) ? ' ON ' . $view['_on'] : '';
+ // 指定JOIN类型 例如 RIGHT INNER LEFT 下一个表有效
+ $type = !empty($view['_type']) ? $view['_type'] : '';
+ $tableName .= ' ' . strtoupper($type) . ' JOIN ';
+ $len = strlen($type . '_JOIN ');
+ }
+ $tableName = substr($tableName, 0, -$len);
+ $this->trueTableName = $tableName;
+ }
+ return $this->trueTableName;
+ }
+
+ /**
+ * 表达式过滤方法
+ * @access protected
+ * @param string $options 表达式
+ * @return void
+ */
+ protected function _options_filter(&$options)
+ {
+ if (isset($options['field'])) {
+ $options['field'] = $this->checkFields($options['field']);
+ } else {
+ $options['field'] = $this->checkFields();
+ }
+
+ if (isset($options['group'])) {
+ $options['group'] = $this->checkGroup($options['group']);
+ }
+
+ if (isset($options['where'])) {
+ $options['where'] = $this->checkCondition($options['where']);
+ }
+
+ if (isset($options['order'])) {
+ $options['order'] = $this->checkOrder($options['order']);
+ }
+
+ }
+
+ /**
+ * 检查是否定义了所有字段
+ * @access protected
+ * @param string $name 模型名称
+ * @param array $fields 字段数组
+ * @return array
+ */
+ private function _checkFields($name, $fields)
+ {
+ if (false !== $pos = array_search('*', $fields)) {
+ // 定义所有字段
+ $fields = array_merge($fields, M($name)->getDbFields());
+ unset($fields[$pos]);
+ }
+ return $fields;
+ }
+
+ /**
+ * 检查条件中的视图字段
+ * @param $where 条件表达式
+ * @return array
+ */
+ protected function checkCondition($where)
+ {
+ if (is_array($where)) {
+ $fields = $field_map_table = array();
+ foreach ($this->viewFields as $key => $val) {
+ $table_alias = isset($val['_as']) ? $val['_as'] : $key;
+ $val = $this->_checkFields($key, $val);
+ foreach ($val as $as_name => $v) {
+ if (is_numeric($as_name)) {
+ $fields[] = $v; //所有表字段集合
+ $field_map_table[] = $table_alias; //所有表字段对应表名集合
+ } else {
+ $fields[$as_name] = $v;
+ $field_map_table[$as_name] = $table_alias;
+ }
+ }
+ }
+ $where = $this->_parseWhere($where, $fields, $field_map_table);
+ }
+
+ return $where;
+ }
+
+ /**
+ * 解析where表达式
+ * @param $where
+ * @param $fields
+ * @param $field_map_table
+ * @return array
+ */
+ private function _parseWhere($where, $fields, $field_map_table)
+ {
+ $view = array();
+ foreach ($where as $name => $val) {
+ if ('_complex' == $name) {
+ //复合查询
+ foreach ($val as $k => $v) {
+ if (false === strpos(substr($k, 0, 1), '_')) {
+ if (false !== $field = array_search($k, $fields, true)) { // 存在视图字段
+ $k = is_numeric($field) ? $field_map_table[$field] . '.' . $k : $field_map_table[$field] . '.' . $field; //字段别名
+ }
+ } else if (is_array($v)) {
+ //数组复合查询
+ $v = $this->_parseWhere($val[$k], $fields, $field_map_table);
+ }
+ $view[$name][$k] = $v;
+ }
+ } else {
+ if (strpos($name, '|')) {
+ //name|title快捷查询
+ $arr = explode('|', $name);
+ foreach ($arr as $k => $v) {
+ if (false !== $field = array_search($v, $fields, true)) {
+ $arr[$k] = is_numeric($field) ? $field_map_table[$field] . '.' . $v : $field_map_table[$field] . '.' . $field;
+ }
+ }
+ $view[implode('|', $arr)] = $val;
+ } else if (strpos($name, '&')) {
+ //name&title快捷查询
+ $arr = explode('&', $name);
+ foreach ($arr as $k => $v) {
+ if (false !== $field = array_search($v, $fields, true)) {
+ $arr[$k] = is_numeric($field) ? $field_map_table[$field] . '.' . $v : $field_map_table[$field] . '.' . $field;
+ }
+ }
+ $view[implode('&', $arr)] = $val;
+ } else {
+ if (false !== $field = array_search($name, $fields, true)) {
+ $name = is_numeric($field) ? $field_map_table[$field] . '.' . $name : $field_map_table[$field] . '.' . $field;
+ }
+ $view[$name] = $val;
+ }
+ }
+ }
+
+ return $view;
+ }
+
+ /**
+ * 检查Order表达式中的视图字段
+ * @access protected
+ * @param string $order 字段
+ * @return string
+ */
+ protected function checkOrder($order = '')
+ {
+ if (is_string($order) && !empty($order)) {
+ $orders = explode(',', $order);
+ $_order = array();
+ foreach ($orders as $order) {
+ $array = explode(' ', trim($order));
+ $field = $array[0];
+ $sort = isset($array[1]) ? $array[1] : 'ASC';
+ // 解析成视图字段
+ foreach ($this->viewFields as $name => $val) {
+ $k = isset($val['_as']) ? $val['_as'] : $name;
+ $val = $this->_checkFields($name, $val);
+ if (false !== $_field = array_search($field, $val, true)) {
+ // 存在视图字段
+ $field = is_numeric($_field) ? $k . '.' . $field : $k . '.' . $_field;
+ break;
+ }
+ }
+ $_order[] = $field . ' ' . $sort;
+ }
+ $order = implode(',', $_order);
+ }
+ return $order;
+ }
+
+ /**
+ * 检查Group表达式中的视图字段
+ * @access protected
+ * @param string $group 字段
+ * @return string
+ */
+ protected function checkGroup($group = '')
+ {
+ if (!empty($group)) {
+ $groups = explode(',', $group);
+ $_group = array();
+ foreach ($groups as $field) {
+ // 解析成视图字段
+ foreach ($this->viewFields as $name => $val) {
+ $k = isset($val['_as']) ? $val['_as'] : $name;
+ $val = $this->_checkFields($name, $val);
+ if (false !== $_field = array_search($field, $val, true)) {
+ // 存在视图字段
+ $field = is_numeric($_field) ? $k . '.' . $field : $k . '.' . $_field;
+ break;
+ }
+ }
+ $_group[] = $field;
+ }
+ $group = implode(',', $_group);
+ }
+ return $group;
+ }
+
+ /**
+ * 检查fields表达式中的视图字段
+ * @access protected
+ * @param string $fields 字段
+ * @return string
+ */
+ protected function checkFields($fields = '')
+ {
+ if (empty($fields) || '*' == $fields) {
+ // 获取全部视图字段
+ $fields = array();
+ foreach ($this->viewFields as $name => $val) {
+ $k = isset($val['_as']) ? $val['_as'] : $name;
+ $val = $this->_checkFields($name, $val);
+ foreach ($val as $key => $field) {
+ if (is_numeric($key)) {
+ $fields[] = $k . '.' . $field . ' AS ' . $field;
+ } elseif ('_' != substr($key, 0, 1)) {
+ // 以_开头的为特殊定义
+ if (false !== strpos($key, '*') || false !== strpos($key, '(') || false !== strpos($key, '.')) {
+ //如果包含* 或者 使用了sql方法 则不再添加前面的表名
+ $fields[] = $key . ' AS ' . $field;
+ } else {
+ $fields[] = $k . '.' . $key . ' AS ' . $field;
+ }
+ }
+ }
+ }
+ $fields = implode(',', $fields);
+ } else {
+ if (!is_array($fields)) {
+ $fields = explode(',', $fields);
+ }
+
+ // 解析成视图字段
+ $array = array();
+ foreach ($fields as $key => $field) {
+ if (strpos($field, '(') || strpos(strtolower($field), ' as ')) {
+ // 使用了函数或者别名
+ $array[] = $field;
+ unset($fields[$key]);
+ }
+ }
+ foreach ($this->viewFields as $name => $val) {
+ $k = isset($val['_as']) ? $val['_as'] : $name;
+ $val = $this->_checkFields($name, $val);
+ foreach ($fields as $key => $field) {
+ if (false !== $_field = array_search($field, $val, true)) {
+ // 存在视图字段
+ if (is_numeric($_field)) {
+ $array[] = $k . '.' . $field . ' AS ' . $field;
+ } elseif ('_' != substr($_field, 0, 1)) {
+ if (false !== strpos($_field, '*') || false !== strpos($_field, '(') || false !== strpos($_field, '.'))
+ //如果包含* 或者 使用了sql方法 则不再添加前面的表名
+ {
+ $array[] = $_field . ' AS ' . $field;
+ } else {
+ $array[] = $k . '.' . $_field . ' AS ' . $field;
+ }
+
+ }
+ }
+ }
+ }
+ $fields = implode(',', $array);
+ }
+ return $fields;
+ }
+}
diff --git a/Framework/Library/Think/Page.class.php b/Framework/Library/Think/Page.class.php
new file mode 100644
index 00000000..bf3d539d
--- /dev/null
+++ b/Framework/Library/Think/Page.class.php
@@ -0,0 +1,150 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+class Page
+{
+ public $firstRow; // 起始行数
+ public $listRows; // 列表每页显示行数
+ public $parameter; // 分页跳转时要带的参数
+ public $totalRows; // 总行数
+ public $totalPages; // 分页总页面数
+ public $rollPage = 11; // 分页栏每页显示的页数
+
+ private $p = 'p'; //分页参数名
+ private $url = ''; //当前链接URL
+ private $nowPage = 1;
+
+ // 分页显示定制
+ private $config = array(
+ 'header' => '共 %TOTAL_ROW% 条记录 ',
+ 'prev' => '<<',
+ 'next' => '>>',
+ 'first' => '1...',
+ 'last' => '...%TOTAL_PAGE%',
+ 'theme' => '%FIRST% %UP_PAGE% %LINK_PAGE% %DOWN_PAGE% %END%',
+ );
+
+ /**
+ * 架构函数
+ * @param array $totalRows 总的记录数
+ * @param array $listRows 每页显示记录数
+ * @param array $parameter 分页跳转的参数
+ */
+ public function __construct($totalRows, $listRows = 20, $parameter = array())
+ {
+ C('VAR_PAGE') && $this->p = C('VAR_PAGE'); //设置分页参数名称
+ /* 基础设置 */
+ $this->totalRows = $totalRows; //设置总记录数
+ $this->listRows = $listRows; //设置每页显示行数
+ $this->parameter = empty($parameter) ? $_GET : $parameter;
+ $this->nowPage = empty($_GET[$this->p]) ? 1 : intval($_GET[$this->p]);
+ $this->nowPage = $this->nowPage > 0 ? $this->nowPage : 1;
+ $this->firstRow = $this->listRows * ($this->nowPage - 1);
+ }
+
+ /**
+ * 定制分页链接设置
+ * @param string $name 设置名称
+ * @param string $value 设置值
+ */
+ public function setConfig($name, $value)
+ {
+ if (isset($this->config[$name])) {
+ $this->config[$name] = $value;
+ }
+ }
+
+ /**
+ * 生成链接URL
+ * @param integer $page 页码
+ * @return string
+ */
+ private function url($page)
+ {
+ return str_replace(urlencode('[PAGE]'), $page, $this->url);
+ }
+
+ /**
+ * 组装分页链接
+ * @return string
+ */
+ public function show()
+ {
+ if (0 == $this->totalRows) {
+ return '';
+ }
+
+ /* 生成URL */
+ $this->parameter[$this->p] = '[PAGE]';
+ $this->url = U(ACTION_NAME, $this->parameter);
+ /* 计算分页信息 */
+ $this->totalPages = ceil($this->totalRows / $this->listRows); //总页数
+ if (!empty($this->totalPages) && $this->nowPage > $this->totalPages) {
+ $this->nowPage = $this->totalPages;
+ }
+
+ /* 计算分页临时变量 */
+ $now_cool_page = $this->rollPage / 2;
+ $now_cool_page_ceil = ceil($now_cool_page);
+
+ //上一页
+ $up_row = $this->nowPage - 1;
+ $up_page = $up_row > 0 ? '' . $this->config['prev'] . ' ' : '';
+
+ //下一页
+ $down_row = $this->nowPage + 1;
+ $down_page = ($down_row <= $this->totalPages) ? '' . $this->config['next'] . ' ' : '';
+
+ //第一页
+ $the_first = '';
+ if ($this->totalPages > $this->rollPage && ($this->nowPage - $now_cool_page) >= 1) {
+ $the_first = '' . $this->config['first'] . ' ';
+ }
+
+ //最后一页
+ $the_end = '';
+ if ($this->totalPages > $this->rollPage && ($this->nowPage + $now_cool_page) < $this->totalPages) {
+ $the_end = '' . $this->config['last'] . ' ';
+ }
+
+ //数字连接
+ $link_page = "";
+ for ($i = 1; $i <= $this->rollPage; $i++) {
+ if (($this->nowPage - $now_cool_page) <= 0) {
+ $page = $i;
+ } elseif (($this->nowPage + $now_cool_page - 1) >= $this->totalPages) {
+ $page = $this->totalPages - $this->rollPage + $i;
+ } else {
+ $page = $this->nowPage - $now_cool_page_ceil + $i;
+ }
+ if ($page > 0 && $page != $this->nowPage) {
+
+ if ($page <= $this->totalPages) {
+ $link_page .= '' . $page . ' ';
+ } else {
+ break;
+ }
+ } else {
+ if ($page > 0 && 1 != $this->totalPages) {
+ $link_page .= '' . $page . ' ';
+ }
+ }
+ }
+
+ //替换分页内容
+ $page_str = str_replace(
+ array('%HEADER%', '%NOW_PAGE%', '%UP_PAGE%', '%DOWN_PAGE%', '%FIRST%', '%LINK_PAGE%', '%END%', '%TOTAL_ROW%', '%TOTAL_PAGE%'),
+ array($this->config['header'], $this->nowPage, $up_page, $down_page, $the_first, $link_page, $the_end, $this->totalRows, $this->totalPages),
+ $this->config['theme']);
+ return "{$page_str}
";
+ }
+}
diff --git a/Framework/Library/Think/Route.class.php b/Framework/Library/Think/Route.class.php
new file mode 100644
index 00000000..5349dbff
--- /dev/null
+++ b/Framework/Library/Think/Route.class.php
@@ -0,0 +1,492 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * ThinkPHP路由解析类
+ */
+class Route
+{
+
+ /**
+ * 路由检测
+ * @param array $paths path_info数组
+ * @return boolean
+ */
+ public static function check($paths = array())
+ {
+ $rules = self::ruleCache();
+ if (!empty($paths)) {
+ $regx = implode('/', $paths);
+ } else {
+ $depr = C('URL_PATHINFO_DEPR');
+ $regx = preg_replace('/\.' . __EXT__ . '$/i', '', trim($_SERVER['PATH_INFO'], $depr));
+ if (!$regx) {
+ return false;
+ }
+ // 分隔符替换 确保路由定义使用统一的分隔符
+ if ('/' != $depr) {
+ $regx = str_replace($depr, '/', $regx);
+ }
+ }
+ // 静态路由检查
+ if (isset($rules[0][$regx])) {
+ $route = $rules[0][$regx];
+ $_SERVER['PATH_INFO'] = $route[0];
+ $args = array_pop($route);
+ if (!empty($route[1])) {
+ $args = array_merge($args, $route[1]);
+ }
+ $_GET = array_merge($args, $_GET);
+ return true;
+ }
+ // 动态路由检查
+ if (!empty($rules[1])) {
+ foreach ($rules[1] as $rule => $route) {
+ $args = array_pop($route);
+ if (isset($route[2])) {
+ // 路由参数检查
+ if (!self::checkOption($route[2], __EXT__)) {
+ continue;
+ }
+ }
+ if ($matches = self::checkUrlMatch($rule, $args, $regx)) {
+ if ($route[0] instanceof \Closure) {
+ // 执行闭包
+ $result = self::invoke($route[0], $matches);
+ // 如果返回布尔值 则继续执行
+ return is_bool($result) ? $result : exit;
+ } else {
+ // 存在动态变量
+ if (strpos($route[0], ':')) {
+ $matches = array_values($matches);
+ $route[0] = preg_replace_callback('/:(\d+)/', function ($match) use (&$matches) {
+ return $matches[$match[1] - 1];
+ }, $route[0]);
+ }
+ // 路由参数关联$matches
+ if ('/' == substr($rule, 0, 1)) {
+ $rule_params = array();
+ foreach($route[1] as $param_key => $param)
+ {
+ list($param_name,$param_value) = explode('=', $param,2);
+ if(!is_null($param_value))
+ {
+ if(preg_match('/^:(\d*)$/',$param_value, $match_index))
+ {
+ $match_index = $match_index[1]-1;
+ $param_value = $matches[$match_index];
+ }
+ $rule_params[$param_name] = $param_value;
+ unset($route[1][$param_key]);
+ }
+ }
+ $route[1] = $rule_params;
+ }
+ // 重定向
+ if ('/' == substr($route[0], 0, 1)) {
+ header("Location: $route[0]", true, $route[1]);
+ exit;
+ } else {
+ $depr = C('URL_PATHINFO_DEPR');
+ if ('/' != $depr) {
+ $route[0] = str_replace('/', $depr, $route[0]);
+ }
+ $_SERVER['PATH_INFO'] = $route[0];
+ if (!empty($route[1])) {
+ $_GET = array_merge($route[1], $_GET);
+ }
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 路由反向解析
+ * @param string $path 控制器/方法
+ * @param array $vars url参数
+ * @param string $depr 分隔符
+ * @param string|true $suffix url后缀
+ * @return string|false
+ */
+ public static function reverse($path, &$vars, $depr, $suffix = true)
+ {
+ static $_rules;
+ if (is_null($_rules)) {
+ if ($rules = self::ruleCache()) {
+ foreach ($rules as $i => $rules2) {
+ foreach ($rules2 as $rule => $route) {
+ if (is_array($route) && is_string($route[0]) && '/' != substr($route[0], 0, 1)) {
+ $_rules[$i][$route[0]][$rule] = $route;
+ }
+ }
+ }
+ }
+ }
+ // 静态路由
+ if (isset($_rules[0][$path])) {
+ foreach ($_rules[0][$path] as $rule => $route) {
+ $args = array_pop($route);
+ if (count($vars) == count($args) && !empty($vars) && !array_diff($vars, $args)) {
+ return str_replace('/', $depr, $rule);
+ }
+ }
+ }
+ if (isset($_rules[1][$path])) {
+ foreach ($_rules[1][$path] as $rule => $route) {
+ $args = array_pop($route);
+ $array = array();
+ if (isset($route[2])) {
+ // 路由参数检查
+ if (!self::checkOption($route[2], $suffix)) {
+ continue;
+ }
+ }
+ if ('/' != substr($rule, 0, 1)) {
+ // 规则路由
+ foreach ($args as $key => $val) {
+ $flag = false;
+ if ($val[0] == 0) {
+ // 静态变量值
+ $array[$key] = $key;
+ continue;
+ }
+ if (isset($vars[$key])) {
+ // 是否有过滤条件
+ if (!empty($val[2])) {
+ if ($val[2] == 'int') {
+ // 是否为数字
+ if (!is_numeric($vars[$key]) || !preg_match('/^\d*$/',$vars[$key])) {
+ break;
+ }
+ } else {
+ // 排除的名称
+ if (in_array($vars[$key], $val[2])) {
+ break;
+ }
+ }
+ }
+ $flag = true;
+ $array[$key] = $vars[$key];
+ } elseif ($val[0] == 1) {
+ // 如果是必选项
+ break;
+ }
+ }
+ // 匹配成功
+ if (!empty($flag)) {
+ foreach (array_keys($array) as $key) {
+ $array[$key] = urlencode($array[$key]);
+ unset($vars[$key]);
+ }
+ return implode($depr, $array);
+ }
+ } else {
+ // 正则路由
+ $keys = !empty($args) ? array_keys($args) : array_keys($vars);
+ $temp_vars = $vars;
+ $str = preg_replace_callback('/\(.*?\)/', function ($match) use (&$temp_vars, &$keys) {
+ $k = array_shift($keys);
+ $re_var = '';
+ if(isset($temp_vars[$k]))
+ {
+ $re_var = $temp_vars[$k];
+ unset($temp_vars[$k]);
+ }
+ return urlencode($re_var);
+ }, $rule);
+ $str = substr($str, 1, -1);
+ $str = rtrim(ltrim($str, '^'), '$');
+ $str = str_replace('\\', '', $str);
+ if (preg_match($rule, $str, $matches)) {
+ // 匹配成功
+ $vars = $temp_vars;
+ return str_replace('/', $depr, $str);
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ // 规则路由定义方法:
+ // '路由规则'=>'[控制器/操作]?额外参数1=值1&额外参数2=值2...'
+ // '路由规则'=>array('[控制器/操作]','额外参数1=值1&额外参数2=值2...')
+ // '路由规则'=>'外部地址'
+ // '路由规则'=>array('外部地址','重定向代码')
+ // 路由规则中 :开头 表示动态变量
+ // 外部地址中可以用动态变量 采用 :1 :2 的方式
+ // 'news/:month/:day/:id'=>array('News/read?cate=1','status=1'),
+ // 'new/:id'=>array('/new.php?id=:1',301), 重定向
+ // 正则路由定义方法:
+ // '路由正则'=>'[控制器/操作]?参数1=值1&参数2=值2...'
+ // '路由正则'=>array('[控制器/操作]?参数1=值1&参数2=值2...','额外参数1=值1&额外参数2=值2...')
+ // '路由正则'=>'外部地址'
+ // '路由正则'=>array('外部地址','重定向代码')
+ // 参数值和外部地址中可以用动态变量 采用 :1 :2 的方式
+ // '/new\/(\d+)\/(\d+)/'=>array('News/read?id=:1&page=:2&cate=1','status=1'),
+ // '/new\/(\d+)/'=>array('/new.php?id=:1&page=:2&status=1','301'), 重定向
+ /**
+ * 读取规则缓存
+ * @param boolean $update 是否更新
+ * @return array
+ */
+ public static function ruleCache($update = false)
+ {
+ $result = array();
+ $module = defined('MODULE_NAME') ? '_' . MODULE_NAME : '';
+ if (APP_DEBUG || $update || !$result = S('url_route_rules' . $module)) {
+ // 静态路由
+ $result[0] = C('URL_MAP_RULES');
+ if (!empty($result[0])) {
+ foreach ($result[0] as $rule => $route) {
+ if (!is_array($route)) {
+ $route = array($route);
+ }
+ if (strpos($route[0], '?')) {
+ // 分离参数
+ list($route[0], $args) = explode('?', $route[0], 2);
+ parse_str($args, $args);
+ } else {
+ $args = array();
+ }
+ if (!empty($route[1]) && is_string($route[1])) {
+ // 额外参数
+ parse_str($route[1], $route[1]);
+ }
+ $route[] = $args;
+ $result[0][$rule] = $route;
+ }
+ }
+ // 动态路由
+ $result[1] = C('URL_ROUTE_RULES');
+ if (!empty($result[1])) {
+ foreach ($result[1] as $rule => $route) {
+ if (!is_array($route)) {
+ $route = array($route);
+ } elseif (is_numeric($rule)) {
+ // 支持 array('rule','adddress',...) 定义路由
+ $rule = array_shift($route);
+ }
+ if (!empty($route)) {
+ $args = array();
+ if (is_string($route[0])) {
+ if (0 === strpos($route[0], '/') || 0 === strpos($route[0], 'http')) {
+ // 重定向
+ if (!isset($route[1])) {
+ $route[1] = 301;
+ }
+ } else {
+ if (!empty($route[1]) && is_string($route[1])) {
+ // 额外参数
+ parse_str($route[1], $route[1]);
+ }
+ if (strpos($route[0], '?')) {
+ // 分离参数
+ list($route[0], $params) = explode('?', $route[0], 2);
+ if (!empty($params)) {
+ foreach (explode('&', $params) as $key => $val) {
+ if (0 === strpos($val, ':')) {
+ // 动态参数
+ $val = substr($val, 1);
+ $args[$key] = strpos($val, '|') ? explode('|', $val, 2) : array($val);
+ } else {
+ $route[1][$key] = $val;
+ }
+ }
+ }
+ }
+ }
+ }
+ if ('/' != substr($rule, 0, 1)) {
+ // 规则路由
+ foreach (explode('/', rtrim($rule, '$')) as $item) {
+ $filter = $fun = '';
+ $type = 0;
+ if (0 === strpos($item, '[:')) {
+ // 可选变量
+ $type = 2;
+ $item = substr($item, 1, -1);
+ }
+ if (0 === strpos($item, ':')) {
+ // 动态变量获取
+ $type = $type ?: 1;
+ if ($pos = strpos($item, '|')) {
+ // 支持函数过滤
+ $fun = substr($item, $pos + 1);
+ $item = substr($item, 1, $pos - 1);
+ }
+ if ($pos = strpos($item, '^')) {
+ // 排除项
+ $filter = explode('-', substr($item, $pos + 1));
+ $item = substr($item, 1, $pos - 1);
+ } elseif (strpos($item, '\\')) {
+ // \d表示限制为数字
+ if ('d' == substr($item, -1)) {
+ $filter = 'int';
+ }
+ $item = substr($item, 1, -2);
+ } else {
+ $item = substr($item, 1);
+ }
+ }
+ $args[$item] = array($type, $fun, $filter);
+ }
+ }
+ $route[] = $args;
+ $result[1][$rule] = $route;
+ } else {
+ unset($result[1][$rule]);
+ }
+ }
+ }
+ S('url_route_rules' . $module, $result);
+ }
+ return $result;
+ }
+
+ /**
+ * 路由参数检测
+ * @param array $options 路由参数
+ * @param string|true $suffix URL后缀
+ * @return boolean
+ */
+ private static function checkOption($options, $suffix = true)
+ {
+ // URL后缀检测
+ if (isset($options['ext'])) {
+ if ($suffix) {
+ $suffix = $suffix === true ? C('URL_HTML_SUFFIX') : $suffix;
+ if ($pos = strpos($suffix, '|')) {
+ $suffix = substr($suffix, 0, $pos);
+ }
+ }
+ if ($suffix != $options['ext']) {
+ return false;
+ }
+ }
+ if (isset($options['method']) && REQUEST_METHOD != strtoupper($options['method'])) {
+ // 请求类型检测
+ return false;
+ }
+ // 自定义检测
+ if (!empty($options['callback']) && is_callable($options['callback'])) {
+ if (false === call_user_func($options['callback'])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 检测URL和路由规则是否匹配
+ * @param string $rule 路由规则
+ * @param array $args 路由动态变量
+ * @param string $regx URL地址
+ * @return array|false
+ */
+ private static function checkUrlMatch(&$rule, &$args, &$regx)
+ {
+ $params = array();
+ if ('/' == substr($rule, 0, 1)) {
+ // 正则路由
+ if (preg_match($rule, $regx, $matches)) {
+ if ($args) { // 存在动态变量
+ foreach ($args as $key => $val) {
+ $params[$key] = isset($val[1]) ? $val[1]($matches[$val[0]]) : $matches[$val[0]];
+ }
+ $regx = substr_replace($regx, '', 0, strlen($matches[0]));
+ }
+ array_shift($matches);
+ return $matches;
+ } else {
+ return false;
+ }
+ } else {
+ $paths = explode('/', $regx);
+ // $结尾则要求完整匹配
+ if ('$' == substr($rule, -1) && count($args) != count($paths)) {
+ return false;
+ }
+ foreach ($args as $key => $val) {
+ $var = array_shift($paths) ?: '';
+ if ($val[0] == 0) {
+ // 静态变量
+ if (0 !== strcasecmp($key, $var)) {
+ return false;
+ }
+ } else {
+ if (isset($val[2])) {
+ // 设置了过滤条件
+ if ($val[2] == 'int') {
+ // 如果值不为整数
+ if (!preg_match('/^\d*$/',$var)) {
+ return false;
+ }
+ } else {
+ // 如果值在排除的名单里
+ if (in_array($var, $val[2])) {
+ return false;
+ }
+ }
+ }
+ if (!empty($var)) {
+ $params[$key] = !empty($val[1]) ? $val[1]($var) : $var;
+ } elseif ($val[0] == 1) {
+ // 不是可选的
+ return false;
+ }
+ }
+ }
+ $matches = $params;
+ $regx = implode('/', $paths);
+ }
+ // 解析剩余的URL参数
+ if ($regx) {
+ preg_replace_callback('/(\w+)\/([^\/]+)/', function ($match) use (&$params) {
+ $params[strtolower($match[1])] = strip_tags($match[2]);
+ }, $regx);
+ }
+ $_GET = array_merge($params, $_GET);
+
+ // 成功匹配后返回URL中的动态变量数组
+ return $matches;
+ }
+
+ /**
+ * 执行闭包方法 支持参数调用
+ * @param function $closure 闭包函数
+ * @param array $var 传给闭包的参数
+ * @return boolean
+ */
+ private static function invoke($closure, $var = array())
+ {
+ $reflect = new \ReflectionFunction($closure);
+ $params = $reflect->getParameters();
+ $args = array();
+ foreach ($params as $i => $param) {
+ $name = $param->getName();
+ if (isset($var[$name])) {
+ $args[] = $var[$name];
+ } elseif (isset($var[$i])) {
+ $args[] = $var[$i];
+ } elseif ($param->isDefaultValueAvailable()) {
+ $args[] = $param->getDefaultValue();
+ }
+ }
+ return $reflect->invokeArgs($args);
+ }
+
+}
\ No newline at end of file
diff --git a/Framework/Library/Think/Session/Driver/Db.class.php b/Framework/Library/Think/Session/Driver/Db.class.php
new file mode 100644
index 00000000..9e275989
--- /dev/null
+++ b/Framework/Library/Think/Session/Driver/Db.class.php
@@ -0,0 +1,193 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Session\Driver;
+
+/**
+ * 数据库方式Session驱动
+ * CREATE TABLE think_session (
+ * session_id varchar(255) NOT NULL,
+ * session_expire int(11) NOT NULL,
+ * session_data blob,
+ * UNIQUE KEY `session_id` (`session_id`)
+ * );
+ */
+class Db
+{
+
+ /**
+ * Session有效时间
+ */
+ protected $lifeTime = '';
+
+ /**
+ * session保存的数据库名
+ */
+ protected $sessionTable = '';
+
+ /**
+ * 数据库句柄
+ */
+ protected $hander = array();
+
+ /**
+ * 打开Session
+ * @access public
+ * @param string $savePath
+ * @param mixed $sessName
+ */
+ public function open($savePath, $sessName)
+ {
+ $this->lifeTime = C('SESSION_EXPIRE') ? C('SESSION_EXPIRE') : ini_get('session.gc_maxlifetime');
+ $this->sessionTable = C('SESSION_TABLE') ? C('SESSION_TABLE') : C("DB_PREFIX") . "session";
+ //分布式数据库
+ $host = explode(',', C('DB_HOST'));
+ $port = explode(',', C('DB_PORT'));
+ $name = explode(',', C('DB_NAME'));
+ $user = explode(',', C('DB_USER'));
+ $pwd = explode(',', C('DB_PWD'));
+ if (1 == C('DB_DEPLOY_TYPE')) {
+ //读写分离
+ if (C('DB_RW_SEPARATE')) {
+ $w = floor(mt_rand(0, C('DB_MASTER_NUM') - 1));
+ if (is_numeric(C('DB_SLAVE_NO'))) {
+//指定服务器读
+ $r = C('DB_SLAVE_NO');
+ } else {
+ $r = floor(mt_rand(C('DB_MASTER_NUM'), count($host) - 1));
+ }
+ //主数据库链接
+ $hander = mysql_connect(
+ $host[$w] . (isset($port[$w]) ? ':' . $port[$w] : ':' . $port[0]),
+ isset($user[$w]) ? $user[$w] : $user[0],
+ isset($pwd[$w]) ? $pwd[$w] : $pwd[0]
+ );
+ $dbSel = mysql_select_db(
+ isset($name[$w]) ? $name[$w] : $name[0]
+ , $hander);
+ if (!$hander || !$dbSel) {
+ return false;
+ }
+
+ $this->hander[0] = $hander;
+ //从数据库链接
+ $hander = mysql_connect(
+ $host[$r] . (isset($port[$r]) ? ':' . $port[$r] : ':' . $port[0]),
+ isset($user[$r]) ? $user[$r] : $user[0],
+ isset($pwd[$r]) ? $pwd[$r] : $pwd[0]
+ );
+ $dbSel = mysql_select_db(
+ isset($name[$r]) ? $name[$r] : $name[0]
+ , $hander);
+ if (!$hander || !$dbSel) {
+ return false;
+ }
+
+ $this->hander[1] = $hander;
+ return true;
+ }
+ }
+ //从数据库链接
+ $r = floor(mt_rand(0, count($host) - 1));
+ $hander = mysql_connect(
+ $host[$r] . (isset($port[$r]) ? ':' . $port[$r] : ':' . $port[0]),
+ isset($user[$r]) ? $user[$r] : $user[0],
+ isset($pwd[$r]) ? $pwd[$r] : $pwd[0]
+ );
+ $dbSel = mysql_select_db(
+ isset($name[$r]) ? $name[$r] : $name[0]
+ , $hander);
+ if (!$hander || !$dbSel) {
+ return false;
+ }
+
+ $this->hander = $hander;
+ return true;
+ }
+
+ /**
+ * 关闭Session
+ * @access public
+ */
+ public function close()
+ {
+ if (is_array($this->hander)) {
+ $this->gc($this->lifeTime);
+ return (mysql_close($this->hander[0]) && mysql_close($this->hander[1]));
+ }
+ $this->gc($this->lifeTime);
+ return mysql_close($this->hander);
+ }
+
+ /**
+ * 读取Session
+ * @access public
+ * @param string $sessID
+ */
+ public function read($sessID)
+ {
+ $hander = is_array($this->hander) ? $this->hander[1] : $this->hander;
+ $res = mysql_query('SELECT session_data AS data FROM ' . $this->sessionTable . " WHERE session_id = '$sessID' AND session_expire >" . time(), $hander);
+ if ($res) {
+ $row = mysql_fetch_assoc($res);
+ return $row['data'];
+ }
+ return "";
+ }
+
+ /**
+ * 写入Session
+ * @access public
+ * @param string $sessID
+ * @param String $sessData
+ */
+ public function write($sessID, $sessData)
+ {
+ $hander = is_array($this->hander) ? $this->hander[0] : $this->hander;
+ $expire = time() + $this->lifeTime;
+ $sessData = addslashes($sessData);
+ mysql_query('REPLACE INTO ' . $this->sessionTable . " ( session_id, session_expire, session_data) VALUES( '$sessID', '$expire', '$sessData')", $hander);
+ if (mysql_affected_rows($hander)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 删除Session
+ * @access public
+ * @param string $sessID
+ */
+ public function destroy($sessID)
+ {
+ $hander = is_array($this->hander) ? $this->hander[0] : $this->hander;
+ mysql_query('DELETE FROM ' . $this->sessionTable . " WHERE session_id = '$sessID'", $hander);
+ if (mysql_affected_rows($hander)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Session 垃圾回收
+ * @access public
+ * @param string $sessMaxLifeTime
+ */
+ public function gc($sessMaxLifeTime)
+ {
+ $hander = is_array($this->hander) ? $this->hander[0] : $this->hander;
+ mysql_query('DELETE FROM ' . $this->sessionTable . ' WHERE session_expire < ' . time(), $hander);
+ return mysql_affected_rows($hander);
+ }
+
+}
diff --git a/Framework/Library/Think/Session/Driver/Memcache.class.php b/Framework/Library/Think/Session/Driver/Memcache.class.php
new file mode 100644
index 00000000..574b504a
--- /dev/null
+++ b/Framework/Library/Think/Session/Driver/Memcache.class.php
@@ -0,0 +1,86 @@
+lifeTime = C('SESSION_EXPIRE') ? C('SESSION_EXPIRE') : $this->lifeTime;
+ // $this->sessionName = $sessName;
+ $options = array(
+ 'timeout' => C('SESSION_TIMEOUT') ? C('SESSION_TIMEOUT') : 1,
+ 'persistent' => C('SESSION_PERSISTENT') ? C('SESSION_PERSISTENT') : 0,
+ );
+ $this->handle = new \Memcache;
+ $hosts = explode(',', C('MEMCACHE_HOST'));
+ $ports = explode(',', C('MEMCACHE_PORT'));
+ foreach ($hosts as $i => $host) {
+ $port = isset($ports[$i]) ? $ports[$i] : $ports[0];
+ $this->handle->addServer($host, $port, true, 1, $options['timeout']);
+ }
+ return true;
+ }
+
+ /**
+ * 关闭Session
+ * @access public
+ */
+ public function close()
+ {
+ $this->gc(ini_get('session.gc_maxlifetime'));
+ $this->handle->close();
+ $this->handle = null;
+ return true;
+ }
+
+ /**
+ * 读取Session
+ * @access public
+ * @param string $sessID
+ */
+ public function read($sessID)
+ {
+ return $this->handle->get($this->sessionName . $sessID);
+ }
+
+ /**
+ * 写入Session
+ * @access public
+ * @param string $sessID
+ * @param String $sessData
+ */
+ public function write($sessID, $sessData)
+ {
+ return $this->handle->set($this->sessionName . $sessID, $sessData, 0, $this->lifeTime);
+ }
+
+ /**
+ * 删除Session
+ * @access public
+ * @param string $sessID
+ */
+ public function destroy($sessID)
+ {
+ return $this->handle->delete($this->sessionName . $sessID);
+ }
+
+ /**
+ * Session 垃圾回收
+ * @access public
+ * @param string $sessMaxLifeTime
+ */
+ public function gc($sessMaxLifeTime)
+ {
+ return true;
+ }
+}
diff --git a/Framework/Library/Think/Session/Driver/Mysqli.class.php b/Framework/Library/Think/Session/Driver/Mysqli.class.php
new file mode 100644
index 00000000..fd3ec06c
--- /dev/null
+++ b/Framework/Library/Think/Session/Driver/Mysqli.class.php
@@ -0,0 +1,196 @@
+ liu21st
+// +----------------------------------------------------------------------
+// | change mysql to mysqli 解决php7没有mysql扩展时数据库存放session无法操作的问题
+// +----------------------------------------------------------------------
+namespace Think\Session\Driver;
+
+/**
+ * 数据库方式Session驱动
+ * CREATE TABLE think_session (
+ * session_id varchar(255) NOT NULL,
+ * session_expire int(11) NOT NULL,
+ * session_data blob,
+ * UNIQUE KEY `session_id` (`session_id`)
+ * );
+ */
+class Mysqli
+{
+
+ /**
+ * Session有效时间
+ */
+ protected $lifeTime = '';
+
+ /**
+ * session保存的数据库名
+ */
+ protected $sessionTable = '';
+
+ /**
+ * 数据库句柄
+ */
+ protected $hander = array();
+
+ /**
+ * 打开Session
+ * @access public
+ * @param string $savePath
+ * @param mixed $sessName
+ */
+ public function open($savePath, $sessName)
+ {
+ $this->lifeTime = C('SESSION_EXPIRE') ? C('SESSION_EXPIRE') : ini_get('session.gc_maxlifetime');
+ $this->sessionTable = C('SESSION_TABLE') ? C('SESSION_TABLE') : C("DB_PREFIX") . "session";
+ //分布式数据库
+ $host = explode(',', C('DB_HOST'));
+ $port = explode(',', C('DB_PORT'));
+ $name = explode(',', C('DB_NAME'));
+ $user = explode(',', C('DB_USER'));
+ $pwd = explode(',', C('DB_PWD'));
+ if (1 == C('DB_DEPLOY_TYPE')) {
+ //读写分离
+ if (C('DB_RW_SEPARATE')) {
+ $w = floor(mt_rand(0, C('DB_MASTER_NUM') - 1));
+ if (is_numeric(C('DB_SLAVE_NO'))) {
+//指定服务器读
+ $r = C('DB_SLAVE_NO');
+ } else {
+ $r = floor(mt_rand(C('DB_MASTER_NUM'), count($host) - 1));
+ }
+ //主数据库链接
+ $hander = mysqli_connect(
+ $host[$w] . (isset($port[$w]) ? ':' . $port[$w] : ':' . $port[0]),
+ isset($user[$w]) ? $user[$w] : $user[0],
+ isset($pwd[$w]) ? $pwd[$w] : $pwd[0]
+ );
+ $dbSel = mysqli_select_db(
+ $hander,
+ isset($name[$w]) ? $name[$w] : $name[0]
+ );
+ if (!$hander || !$dbSel) {
+ return false;
+ }
+
+ $this->hander[0] = $hander;
+ //从数据库链接
+ $hander = mysqli_connect(
+ $host[$r] . (isset($port[$r]) ? ':' . $port[$r] : ':' . $port[0]),
+ isset($user[$r]) ? $user[$r] : $user[0],
+ isset($pwd[$r]) ? $pwd[$r] : $pwd[0]
+ );
+ $dbSel = mysqli_select_db(
+ $hander,
+ isset($name[$r]) ? $name[$r] : $name[0]
+ );
+ if (!$hander || !$dbSel) {
+ return false;
+ }
+
+ $this->hander[1] = $hander;
+ return true;
+ }
+ }
+ //从数据库链接
+ $r = floor(mt_rand(0, count($host) - 1));
+ $hander = mysqli_connect(
+ $host[$r] . (isset($port[$r]) ? ':' . $port[$r] : ':' . $port[0]),
+ isset($user[$r]) ? $user[$r] : $user[0],
+ isset($pwd[$r]) ? $pwd[$r] : $pwd[0]
+ );
+ $dbSel = mysqli_select_db(
+ $hander,
+ isset($name[$r]) ? $name[$r] : $name[0]
+ );
+ if (!$hander || !$dbSel) {
+ return false;
+ }
+
+ $this->hander = $hander;
+ return true;
+ }
+
+ /**
+ * 关闭Session
+ * @access public
+ */
+ public function close()
+ {
+ if (is_array($this->hander)) {
+ $this->gc($this->lifeTime);
+ return (mysqli_close($this->hander[0]) && mysqli_close($this->hander[1]));
+ }
+ $this->gc($this->lifeTime);
+ return mysqli_close($this->hander);
+ }
+
+ /**
+ * 读取Session
+ * @access public
+ * @param string $sessID
+ */
+ public function read($sessID)
+ {
+ $hander = is_array($this->hander) ? $this->hander[1] : $this->hander;
+ $res = mysqli_query($hander, "SELECT session_data AS data FROM " . $this->sessionTable . " WHERE session_id = '$sessID' AND session_expire >" . time());
+ if ($res) {
+ $row = mysqli_fetch_assoc($res);
+ return $row['data'];
+ }
+ return "";
+ }
+
+ /**
+ * 写入Session
+ * @access public
+ * @param string $sessID
+ * @param String $sessData
+ */
+ public function write($sessID, $sessData)
+ {
+ $hander = is_array($this->hander) ? $this->hander[0] : $this->hander;
+ $expire = time() + $this->lifeTime;
+ mysqli_query($hander, "REPLACE INTO " . $this->sessionTable . " ( session_id, session_expire, session_data) VALUES( '$sessID', '$expire', '$sessData')");
+ if (mysqli_affected_rows($hander)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 删除Session
+ * @access public
+ * @param string $sessID
+ */
+ public function destroy($sessID)
+ {
+ $hander = is_array($this->hander) ? $this->hander[0] : $this->hander;
+ mysqli_query($hander, "DELETE FROM " . $this->sessionTable . " WHERE session_id = '$sessID'");
+ if (mysqli_affected_rows($hander)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Session 垃圾回收
+ * @access public
+ * @param string $sessMaxLifeTime
+ */
+ public function gc($sessMaxLifeTime)
+ {
+ $hander = is_array($this->hander) ? $this->hander[0] : $this->hander;
+ mysqli_query($hander, "DELETE FROM " . $this->sessionTable . " WHERE session_expire < " . time());
+ return mysqli_affected_rows($hander);
+ }
+
+}
diff --git a/Framework/Library/Think/Session/Driver/Sql.class.php b/Framework/Library/Think/Session/Driver/Sql.class.php
new file mode 100644
index 00000000..5755d177
--- /dev/null
+++ b/Framework/Library/Think/Session/Driver/Sql.class.php
@@ -0,0 +1,193 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Session\Driver;
+use PDO;
+/**
+ * 数据库方式Session驱动
+ * CREATE TABLE think_session (
+ * session_id varchar(255) NOT NULL,
+ * session_expire int(11) NOT NULL,
+ * session_data blob,
+ * UNIQUE KEY `session_id` (`session_id`)
+ * );.
+ */
+class Sql {
+ /**
+ * Session有效时间.
+ */
+ protected $lifeTime = '';
+
+ /**
+ * session保存的数据库名.
+ */
+ protected $sessionTable = '';
+
+ /**
+ * 数据库句柄.
+ */
+ protected $hander = array();
+
+ // PDO连接参数
+ protected $options = array(PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, PDO::ATTR_STRINGIFY_FETCHES => false);
+
+ /**
+ * 解析pdo连接的dsn信息.
+ *
+ * @param unknown $name 数据库名称
+ * @param unknown $host 数据库地址
+ * @param unknown $port 端口
+ * @param unknown $socket socket
+ * @param unknown $charset 字符集
+ *
+ * @return string
+ */
+ protected function parseDsn($name, $host = '127.0.0.1', $port = '', $socket = '', $charset = '') {
+ $dsn = 'mysql:dbname=' . $name . ';host=' . $host;
+ if (!empty($port)) {
+ $dsn.= ';port=' . $port;
+ } elseif (!empty($socket)) {
+ $dsn.= ';unix_socket=' . $socket;
+ }
+ if (!empty($charset)) {
+ //为兼容各版本PHP,用两种方式设置编码
+ $this->options[\PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES ' . $charset;
+ $dsn.= ';charset=' . $charset;
+ }
+ return $dsn;
+ }
+
+ /**
+ * 打开Session.
+ *
+ * @param string $savePath
+ * @param mixed $sessName
+ */
+ public function open($savePath, $sessName) {
+ $this->lifeTime = C('SESSION_EXPIRE') ? C('SESSION_EXPIRE') : ini_get('session.gc_maxlifetime');
+ $this->sessionTable = C('SESSION_TABLE') ? C('SESSION_TABLE') : C('DB_PREFIX') . 'session';
+ //分布式数据库
+ $host = explode(',', C('DB_HOST'));
+ $port = explode(',', C('DB_PORT'));
+ $name = explode(',', C('DB_NAME'));
+ $user = explode(',', C('DB_USER'));
+ $pwd = explode(',', C('DB_PWD'));
+ if (1 == C('DB_DEPLOY_TYPE')) {
+ //读写分离
+ if (C('DB_RW_SEPARATE')) {
+ $w = floor(mt_rand(0, C('DB_MASTER_NUM') - 1));
+ if (is_numeric(C('DB_SLAVE_NO'))) { //指定服务器读
+ $r = C('DB_SLAVE_NO');
+ } else {
+ $r = floor(mt_rand(C('DB_MASTER_NUM'), count($host) - 1));
+ }
+ //主数据库链接
+ $dsn = $this->parseDsn((isset($name[$w]) ? $name[$w] : $name[0]), $host[$w], (isset($port[$w]) ? $port[$w] : $port[0]));
+ $hander = new PDO($dsn, (isset($user[$w]) ? $user[$w] : $user[0]), (isset($pwd[$w]) ? $pwd[$w] : $pwd[0]));
+ if (!$hander) {
+ return false;
+ }
+ $this->hander[0] = $hander;
+ //从数据库链接
+ $dsn = $this->parseDsn((isset($name[$r]) ? $name[$r] : $name[0]), $host[$r], (isset($port[$r]) ? $port[$r] : $port[0]));
+ $hander = new PDO($dsn, (isset($user[$r]) ? $user[$r] : $user[0]), (isset($pwd[$r]) ? $pwd[$r] : $pwd[0]));
+ if (!$hander) {
+ return false;
+ }
+ $this->hander[1] = $hander;
+ return true;
+ }
+ }
+ //从数据库链接
+ $r = floor(mt_rand(0, count($host) - 1));
+ $dsn = $this->parseDsn((isset($name[$r]) ? $name[$r] : $name[0]), $host[$r], (isset($port[$r]) ? $port[$r] : $port[0]));
+ $hander = new PDO($dsn, (isset($user[$r]) ? $user[$r] : $user[0]), (isset($pwd[$r]) ? $pwd[$r] : $pwd[0]));
+ if (!$hander) {
+ return false;
+ }
+ $this->hander = $hander;
+ return true;
+ }
+
+ /**
+ * 关闭Session.
+ */
+ public function close() {
+ if (is_array($this->hander)) {
+ $this->gc($this->lifeTime);
+ return ($this->hander[0] = null) && ($this->hander[1] = null);
+ }
+ $this->gc($this->lifeTime);
+ return $this->hander = null;
+ }
+
+ /**
+ * 读取Session.
+ *
+ * @param string $sessID
+ */
+ public function read($sessID) {
+ $hander = is_array($this->hander) ? $this->hander[1] : $this->hander;
+ $res = $hander->prepare('SELECT session_data AS data FROM ' . $this->sessionTable . " WHERE session_id = '$sessID' AND session_expire >" . time());
+ $res->execute();
+ if ($result = $res->fetch(PDO::FETCH_ASSOC)) {
+ return $result['data'];
+ }
+ return '';
+ }
+
+ /**
+ * 写入Session.
+ *
+ * @param string $sessID
+ * @param string $sessData
+ */
+ public function write($sessID, $sessData) {
+ $hander = is_array($this->hander) ? $this->hander[0] : $this->hander;
+ $expire = time() + $this->lifeTime;
+ $sessData = addslashes($sessData);
+ $res = $hander->prepare("SELECT COUNT(*) FROM " . $this->sessionTable . " WHERE `session_id` = '$sessID'");
+ $res->execute();
+ $result = $res->fetch(PDO::FETCH_ASSOC);
+ if ($result['COUNT(*)'] === '1') {
+ $res = $hander->exec('UPDATE ' . $this->sessionTable . " SET `session_data` = '$sessData', `session_expire` = '$expire', `update_time` = '" . time() . "' WHERE `session_id` = '$sessID'");
+ } else {
+ $res = $hander->exec('INSERT INTO ' . $this->sessionTable . " ( session_id, session_expire, session_data, update_time) VALUES( '$sessID', '$expire', '$sessData', '" . time() . "')");
+ }
+ if ($res) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 删除Session.
+ *
+ * @param string $sessID
+ */
+ public function destroy($sessID) {
+ $hander = is_array($this->hander) ? $this->hander[0] : $this->hander;
+ $res = $hander->exec('DELETE FROM ' . $this->sessionTable . " WHERE session_id = '$sessID'");
+ if ($res) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Session 垃圾回收.
+ *
+ * @param string $sessMaxLifeTime
+ */
+ public function gc($sessMaxLifeTime) {
+ $hander = is_array($this->hander) ? $this->hander[0] : $this->hander;
+ return $hander->exec('DELETE FROM ' . $this->sessionTable . ' WHERE session_expire < ' . time());
+ }
+}
diff --git a/Framework/Library/Think/Storage.class.php b/Framework/Library/Think/Storage.class.php
new file mode 100644
index 00000000..7e514006
--- /dev/null
+++ b/Framework/Library/Think/Storage.class.php
@@ -0,0 +1,44 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+// 分布式文件存储类
+class Storage
+{
+
+ /**
+ * 操作句柄
+ * @var string
+ * @access protected
+ */
+ protected static $handler;
+
+ /**
+ * 连接分布式文件系统
+ * @access public
+ * @param string $type 文件类型
+ * @param array $options 配置数组
+ * @return void
+ */
+ public static function connect($type = 'File', $options = array())
+ {
+ $class = 'Think\\Storage\\Driver\\' . ucwords($type);
+ self::$handler = new $class($options);
+ }
+
+ public static function __callStatic($method, $args)
+ {
+ //调用缓存驱动的方法
+ if (method_exists(self::$handler, $method)) {
+ return call_user_func_array(array(self::$handler, $method), $args);
+ }
+ }
+}
diff --git a/Framework/Library/Think/Storage/Driver/File.class.php b/Framework/Library/Think/Storage/Driver/File.class.php
new file mode 100644
index 00000000..2c71ca80
--- /dev/null
+++ b/Framework/Library/Think/Storage/Driver/File.class.php
@@ -0,0 +1,137 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Storage\Driver;
+
+use Think\Storage;
+
+// 本地文件写入存储类
+class File extends Storage
+{
+
+ private $contents = array();
+
+ /**
+ * 架构函数
+ * @access public
+ */
+ public function __construct()
+ {
+ }
+
+ /**
+ * 文件内容读取
+ * @access public
+ * @param string $filename 文件名
+ * @return string
+ */
+ public function read($filename, $type = '')
+ {
+ return $this->get($filename, 'content', $type);
+ }
+
+ /**
+ * 文件写入
+ * @access public
+ * @param string $filename 文件名
+ * @param string $content 文件内容
+ * @return boolean
+ */
+ public function put($filename, $content, $type = '')
+ {
+ $dir = dirname($filename);
+ if (!is_dir($dir)) {
+ mkdir($dir, 0777, true);
+ }
+ if (false === file_put_contents($filename, $content)) {
+ E(L('_STORAGE_WRITE_ERROR_') . ':' . $filename);
+ } else {
+ $this->contents[$filename] = $content;
+ return true;
+ }
+ }
+
+ /**
+ * 文件追加写入
+ * @access public
+ * @param string $filename 文件名
+ * @param string $content 追加的文件内容
+ * @return boolean
+ */
+ public function append($filename, $content, $type = '')
+ {
+ if (is_file($filename)) {
+ $content = $this->read($filename, $type) . $content;
+ }
+ return $this->put($filename, $content, $type);
+ }
+
+ /**
+ * 加载文件
+ * @access public
+ * @param string $filename 文件名
+ * @param array $vars 传入变量
+ * @return void
+ */
+ public function load($_filename, $vars = null)
+ {
+ if (!is_null($vars)) {
+ extract($vars, EXTR_OVERWRITE);
+ }
+ include $_filename;
+ }
+
+ /**
+ * 文件是否存在
+ * @access public
+ * @param string $filename 文件名
+ * @return boolean
+ */
+ public function has($filename, $type = '')
+ {
+ return is_file($filename);
+ }
+
+ /**
+ * 文件删除
+ * @access public
+ * @param string $filename 文件名
+ * @return boolean
+ */
+ public function unlink($filename, $type = '')
+ {
+ unset($this->contents[$filename]);
+ return is_file($filename) ? unlink($filename) : false;
+ }
+
+ /**
+ * 读取文件信息
+ * @access public
+ * @param string $filename 文件名
+ * @param string $name 信息名 mtime或者content
+ * @return boolean
+ */
+ public function get($filename, $name, $type = '')
+ {
+ if (!isset($this->contents[$filename])) {
+ if (!is_file($filename)) {
+ return false;
+ }
+
+ $this->contents[$filename] = file_get_contents($filename);
+ }
+ $content = $this->contents[$filename];
+ $info = array(
+ 'mtime' => filemtime($filename),
+ 'content' => $content,
+ );
+ return $info[$name];
+ }
+}
diff --git a/Framework/Library/Think/Storage/Driver/Sae.class.php b/Framework/Library/Think/Storage/Driver/Sae.class.php
new file mode 100644
index 00000000..2c2dbffa
--- /dev/null
+++ b/Framework/Library/Think/Storage/Driver/Sae.class.php
@@ -0,0 +1,208 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Storage\Driver;
+
+use Think\Storage;
+
+// SAE环境文件写入存储类
+class Sae extends Storage
+{
+
+ /**
+ * 架构函数
+ * @access public
+ */
+ private $mc;
+ private $kvs = array();
+ private $htmls = array();
+ private $contents = array();
+ public function __construct()
+ {
+ if (!function_exists('memcache_init')) {
+ header('Content-Type:text/html;charset=utf-8');
+ exit('请在SAE平台上运行代码。');
+ }
+ $this->mc = @memcache_init();
+ if (!$this->mc) {
+ header('Content-Type:text/html;charset=utf-8');
+ exit('您未开通Memcache服务,请在SAE管理平台初始化Memcache服务');
+ }
+ }
+
+ /**
+ * 获得SaeKv对象
+ */
+ private function getKv()
+ {
+ static $kv;
+ if (!$kv) {
+ $kv = new \SaeKV();
+ if (!$kv->init()) {
+ E('您没有初始化KVDB,请在SAE管理平台初始化KVDB服务');
+ }
+
+ }
+ return $kv;
+ }
+
+ /**
+ * 文件内容读取
+ * @access public
+ * @param string $filename 文件名
+ * @return string
+ */
+ public function read($filename, $type = '')
+ {
+ switch (strtolower($type)) {
+ case 'f':
+ $kv = $this->getKv();
+ if (!isset($this->kvs[$filename])) {
+ $this->kvs[$filename] = $kv->get($filename);
+ }
+ return $this->kvs[$filename];
+ default:
+ return $this->get($filename, 'content', $type);
+ }
+ }
+
+ /**
+ * 文件写入
+ * @access public
+ * @param string $filename 文件名
+ * @param string $content 文件内容
+ * @return boolean
+ */
+ public function put($filename, $content, $type = '')
+ {
+ switch (strtolower($type)) {
+ case 'f':
+ $kv = $this->getKv();
+ $this->kvs[$filename] = $content;
+ return $kv->set($filename, $content);
+ case 'html':
+ $kv = $this->getKv();
+ $content = time() . $content;
+ $this->htmls[$filename] = $content;
+ return $kv->set($filename, $content);
+ default:
+ $content = time() . $content;
+ if (!$this->mc->set($filename, $content, MEMCACHE_COMPRESSED, 0)) {
+ E(L('_STORAGE_WRITE_ERROR_') . ':' . $filename);
+ } else {
+ $this->contents[$filename] = $content;
+ return true;
+ }
+ }
+ }
+
+ /**
+ * 文件追加写入
+ * @access public
+ * @param string $filename 文件名
+ * @param string $content 追加的文件内容
+ * @return boolean
+ */
+ public function append($filename, $content, $type = '')
+ {
+ if ($old_content = $this->read($filename, $type)) {
+ $content = $old_content . $content;
+ }
+ return $this->put($filename, $content, $type);
+ }
+
+ /**
+ * 加载文件
+ * @access public
+ * @param string $_filename 文件名
+ * @param array $vars 传入变量
+ * @return void
+ */
+ public function load($_filename, $vars = null)
+ {
+ if (!is_null($vars)) {
+ extract($vars, EXTR_OVERWRITE);
+ }
+
+ eval('?>' . $this->read($_filename));
+ }
+
+ /**
+ * 文件是否存在
+ * @access public
+ * @param string $filename 文件名
+ * @return boolean
+ */
+ public function has($filename, $type = '')
+ {
+ if ($this->read($filename, $type)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * 文件删除
+ * @access public
+ * @param string $filename 文件名
+ * @return boolean
+ */
+ public function unlink($filename, $type = '')
+ {
+ switch (strtolower($type)) {
+ case 'f':
+ $kv = $this->getKv();
+ unset($this->kvs[$filename]);
+ return $kv->delete($filename);
+ case 'html':
+ $kv = $this->getKv();
+ unset($this->htmls[$filename]);
+ return $kv->delete($filename);
+ default:
+ unset($this->contents[$filename]);
+ return $this->mc->delete($filename);
+ }
+ }
+
+ /**
+ * 读取文件信息
+ * @access public
+ * @param string $filename 文件名
+ * @param string $name 信息名 mtime或者content
+ * @return boolean
+ */
+ public function get($filename, $name, $type = '')
+ {
+ switch (strtolower($type)) {
+ case 'html':
+ if (!isset($this->htmls[$filename])) {
+ $kv = $this->getKv();
+ $this->htmls[$filename] = $kv->get($filename);
+ }
+ $content = $this->htmls[$filename];
+ break;
+ default:
+ if (!isset($this->contents[$filename])) {
+ $this->contents[$filename] = $this->mc->get($filename);
+ }
+ $content = $this->contents[$filename];
+ }
+ if (false === $content) {
+ return false;
+ }
+ $info = array(
+ 'mtime' => substr($content, 0, 10),
+ 'content' => substr($content, 10),
+ );
+ return $info[$name];
+ }
+
+}
diff --git a/Framework/Library/Think/Template.class.php b/Framework/Library/Think/Template.class.php
new file mode 100644
index 00000000..3c82a798
--- /dev/null
+++ b/Framework/Library/Think/Template.class.php
@@ -0,0 +1,789 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * ThinkPHP内置模板引擎类
+ * 支持XML标签和普通标签的模板解析
+ * 编译型模板引擎 支持动态缓存
+ */
+use Think\Hook as Hook;
+//use Think\Crypt\Driver\Think as Think;
+use Think\Storage as Storage;
+use Think\Think as Think;
+
+class Template
+{
+
+ // 模板页面中引入的标签库列表
+ protected $tagLib = array();
+ // 当前模板文件
+ protected $templateFile = '';
+ // 模板变量
+ public $tVar = array();
+ public $config = array();
+ private $literal = array();
+ private $block = array();
+
+ /**
+ * 架构函数
+ * @access public
+ */
+ public function __construct()
+ {
+ $this->config['cache_path'] = C('CACHE_PATH');
+ $this->config['template_suffix'] = C('TMPL_TEMPLATE_SUFFIX');
+ $this->config['cache_suffix'] = C('TMPL_CACHFILE_SUFFIX');
+ $this->config['tmpl_cache'] = C('TMPL_CACHE_ON');
+ $this->config['cache_time'] = C('TMPL_CACHE_TIME');
+ $this->config['taglib_begin'] = $this->stripPreg(C('TAGLIB_BEGIN'));
+ $this->config['taglib_end'] = $this->stripPreg(C('TAGLIB_END'));
+ $this->config['tmpl_begin'] = $this->stripPreg(C('TMPL_L_DELIM'));
+ $this->config['tmpl_end'] = $this->stripPreg(C('TMPL_R_DELIM'));
+ $this->config['default_tmpl'] = C('TEMPLATE_NAME');
+ $this->config['layout_item'] = C('TMPL_LAYOUT_ITEM');
+ }
+
+ private function stripPreg($str)
+ {
+ return str_replace(
+ array('{', '}', '(', ')', '|', '[', ']', '-', '+', '*', '.', '^', '?'),
+ array('\{', '\}', '\(', '\)', '\|', '\[', '\]', '\-', '\+', '\*', '\.', '\^', '\?'),
+ $str);
+ }
+
+ // 模板变量获取和设置
+ public function get($name)
+ {
+ if (isset($this->tVar[$name])) {
+ return $this->tVar[$name];
+ } else {
+ return false;
+ }
+
+ }
+
+ public function set($name, $value)
+ {
+ $this->tVar[$name] = $value;
+ }
+
+ /**
+ * 加载模板
+ * @access public
+ * @param string $templateFile 模板文件
+ * @param array $templateVar 模板变量
+ * @param string $prefix 模板标识前缀
+ * @return void
+ */
+ public function fetch($templateFile, $templateVar, $prefix = '')
+ {
+ $this->tVar = $templateVar;
+ $templateCacheFile = $this->loadTemplate($templateFile, $prefix);
+ Storage::load($templateCacheFile, $this->tVar, null, 'tpl');
+ }
+
+ /**
+ * 加载主模板并缓存
+ * @access public
+ * @param string $templateFile 模板文件
+ * @param string $prefix 模板标识前缀
+ * @return string
+ * @throws ThinkExecption
+ */
+ public function loadTemplate($templateFile, $prefix = '')
+ {
+ if (is_file($templateFile)) {
+ $this->templateFile = $templateFile;
+ // 读取模板文件内容
+ $tmplContent = file_get_contents($templateFile);
+ } else {
+ $tmplContent = $templateFile;
+ }
+ // 根据模版文件名定位缓存文件
+ $tmplCacheFile = $this->config['cache_path'] . $prefix . md5($templateFile) . $this->config['cache_suffix'];
+
+ // 判断是否启用布局
+ if (C('LAYOUT_ON')) {
+ if (false !== strpos($tmplContent, '{__NOLAYOUT__}')) {
+ // 可以单独定义不使用布局
+ $tmplContent = str_replace('{__NOLAYOUT__}', '', $tmplContent);
+ } else {
+ // 替换布局的主体内容
+ $layoutFile = THEME_PATH . C('LAYOUT_NAME') . $this->config['template_suffix'];
+ // 检查布局文件
+ if (!is_file($layoutFile)) {
+ E(L('_TEMPLATE_NOT_EXIST_') . ':' . $layoutFile);
+ }
+ $tmplContent = str_replace($this->config['layout_item'], $tmplContent, file_get_contents($layoutFile));
+ }
+ }
+ // 编译模板内容
+ $tmplContent = $this->compiler($tmplContent);
+ Storage::put($tmplCacheFile, trim($tmplContent), 'tpl');
+ return $tmplCacheFile;
+ }
+
+ /**
+ * 编译模板文件内容
+ * @access protected
+ * @param mixed $tmplContent 模板内容
+ * @return string
+ */
+ protected function compiler($tmplContent)
+ {
+ //模板解析
+ $tmplContent = $this->parse($tmplContent);
+ // 还原被替换的Literal标签
+ $tmplContent = preg_replace_callback('//is', array($this, 'restoreLiteral'), $tmplContent);
+ // 添加安全代码
+ $tmplContent = '' . $tmplContent;
+ // 优化生成的php代码
+ $tmplContent = str_replace('?>config['taglib_begin'];
+ $end = $this->config['taglib_end'];
+ // 检查include语法
+ $content = $this->parseInclude($content);
+ // 检查PHP语法
+ $content = $this->parsePhp($content);
+ // 首先替换literal标签内容
+ $content = preg_replace_callback('/' . $begin . 'literal' . $end . '(.*?)' . $begin . '\/literal' . $end . '/is', array($this, 'parseLiteral'), $content);
+
+ // 获取需要引入的标签库列表
+ // 标签库只需要定义一次,允许引入多个一次
+ // 一般放在文件的最前面
+ // 格式:
+ // 当TAGLIB_LOAD配置为true时才会进行检测
+ if (C('TAGLIB_LOAD')) {
+ $this->getIncludeTagLib($content);
+ if (!empty($this->tagLib)) {
+ // 对导入的TagLib进行解析
+ foreach ($this->tagLib as $tagLibName) {
+ $this->parseTagLib($tagLibName, $content);
+ }
+ }
+ }
+ // 预先加载的标签库 无需在每个模板中使用taglib标签加载 但必须使用标签库XML前缀
+ if (C('TAGLIB_PRE_LOAD')) {
+ $tagLibs = explode(',', C('TAGLIB_PRE_LOAD'));
+ foreach ($tagLibs as $tag) {
+ $this->parseTagLib($tag, $content);
+ }
+ }
+ // 内置标签库 无需使用taglib标签导入就可以使用 并且不需使用标签库XML前缀
+ $tagLibs = explode(',', C('TAGLIB_BUILD_IN'));
+ foreach ($tagLibs as $tag) {
+ $this->parseTagLib($tag, $content, true);
+ }
+ //解析普通模板标签 {$tagName}
+ $content = preg_replace_callback('/(' . $this->config['tmpl_begin'] . ')([^\d\w\s' . $this->config['tmpl_begin'] . $this->config['tmpl_end'] . '].+?)(' . $this->config['tmpl_end'] . ')/is', array($this, 'parseTag'), $content);
+ return $content;
+ }
+
+ // 检查PHP语法
+ protected function parsePhp($content)
+ {
+ if (ini_get('short_open_tag')) {
+ // 开启短标签的情况要将' . "\n", $content);
+ }
+ // PHP语法检查
+ if (C('TMPL_DENY_PHP') && false !== strpos($content, 'config['taglib_begin'] . 'layout\s(.+?)\s*?\/' . $this->config['taglib_end'] . '/is', $content, $matches);
+ if ($find) {
+ //替换Layout标签
+ $content = str_replace($matches[0], '', $content);
+ //解析Layout标签
+ $array = $this->parseXmlAttrs($matches[1]);
+ if (!C('LAYOUT_ON') || C('LAYOUT_NAME') != $array['name']) {
+ // 读取布局模板
+ $layoutFile = THEME_PATH . $array['name'] . $this->config['template_suffix'];
+ $replace = isset($array['replace']) ? $array['replace'] : $this->config['layout_item'];
+ // 替换布局的主体内容
+ $content = str_replace($replace, $content, file_get_contents($layoutFile));
+ }
+ } else {
+ $content = str_replace('{__NOLAYOUT__}', '', $content);
+ }
+ return $content;
+ }
+
+ // 解析模板中的include标签
+ protected function parseInclude($content, $extend = true)
+ {
+ // 解析继承
+ if ($extend) {
+ $content = $this->parseExtend($content);
+ }
+
+ // 解析布局
+ $content = $this->parseLayout($content);
+ // 读取模板中的include标签
+ $find = preg_match_all('/' . $this->config['taglib_begin'] . 'include\s(.+?)\s*?\/' . $this->config['taglib_end'] . '/is', $content, $matches);
+ if ($find) {
+ for ($i = 0; $i < $find; $i++) {
+ $include = $matches[1][$i];
+ $array = $this->parseXmlAttrs($include);
+ $file = $array['file'];
+ unset($array['file']);
+ $content = str_replace($matches[0][$i], $this->parseIncludeItem($file, $array, $extend), $content);
+ }
+ }
+ return $content;
+ }
+
+ // 解析模板中的extend标签
+ protected function parseExtend($content)
+ {
+ $begin = $this->config['taglib_begin'];
+ $end = $this->config['taglib_end'];
+ // 读取模板中的继承标签
+ $find = preg_match('/' . $begin . 'extend\s(.+?)\s*?\/' . $end . '/is', $content, $matches);
+ if ($find) {
+ //替换extend标签
+ $content = str_replace($matches[0], '', $content);
+ // 记录页面中的block标签
+ preg_replace_callback('/' . $begin . 'block\sname=[\'"](.+?)[\'"]\s*?' . $end . '(.*?)' . $begin . '\/block' . $end . '/is', array($this, 'parseBlock'), $content);
+ // 读取继承模板
+ $array = $this->parseXmlAttrs($matches[1]);
+ $content = $this->parseTemplateName($array['name']);
+ $content = $this->parseInclude($content, false); //对继承模板中的include进行分析
+ // 替换block标签
+ $content = $this->replaceBlock($content);
+ } else {
+ $content = preg_replace_callback('/' . $begin . 'block\sname=[\'"](.+?)[\'"]\s*?' . $end . '(.*?)' . $begin . '\/block' . $end . '/is', function ($match) {return stripslashes($match[2]);}, $content);
+ }
+ return $content;
+ }
+
+ /**
+ * 分析XML属性
+ * @access private
+ * @param string $attrs XML属性字符串
+ * @return array
+ */
+ private function parseXmlAttrs($attrs)
+ {
+ $xml = ' ';
+ $xml = simplexml_load_string($xml);
+ if (!$xml) {
+ E(L('_XML_TAG_ERROR_'));
+ }
+
+ $xml = (array) ($xml->tag->attributes());
+ $array = array_change_key_case($xml['@attributes']);
+ return $array;
+ }
+
+ /**
+ * 替换页面中的literal标签
+ * @access private
+ * @param string $content 模板内容
+ * @return string|false
+ */
+ private function parseLiteral($content)
+ {
+ if (is_array($content)) {
+ $content = $content[1];
+ }
+
+ if (trim($content) == '') {
+ return '';
+ }
+
+ //$content = stripslashes($content);
+ $i = count($this->literal);
+ $parseStr = "";
+ $this->literal[$i] = $content;
+ return $parseStr;
+ }
+
+ /**
+ * 还原被替换的literal标签
+ * @access private
+ * @param string $tag literal标签序号
+ * @return string|false
+ */
+ private function restoreLiteral($tag)
+ {
+ if (is_array($tag)) {
+ $tag = $tag[1];
+ }
+
+ // 还原literal标签
+ $parseStr = $this->literal[$tag];
+ // 销毁literal记录
+ unset($this->literal[$tag]);
+ return $parseStr;
+ }
+
+ /**
+ * 记录当前页面中的block标签
+ * @access private
+ * @param string $name block名称
+ * @param string $content 模板内容
+ * @return string
+ */
+ private function parseBlock($name, $content = '')
+ {
+ if (is_array($name)) {
+ $content = $name[2];
+ $name = $name[1];
+ }
+ $this->block[$name] = $content;
+ return '';
+ }
+
+ /**
+ * 替换继承模板中的block标签
+ * @access private
+ * @param string $content 模板内容
+ * @return string
+ */
+ private function replaceBlock($content)
+ {
+ static $parse = 0;
+ $begin = $this->config['taglib_begin'];
+ $end = $this->config['taglib_end'];
+ $reg = '/(' . $begin . 'block\sname=[\'"](.+?)[\'"]\s*?' . $end . ')(.*?)' . $begin . '\/block' . $end . '/is';
+ if (is_string($content)) {
+ do {
+ $content = preg_replace_callback($reg, array($this, 'replaceBlock'), $content);
+ } while ($parse && $parse--);
+ return $content;
+ } elseif (is_array($content)) {
+ if (preg_match('/' . $begin . 'block\sname=[\'"](.+?)[\'"]\s*?' . $end . '/is', $content[3])) {
+ //存在嵌套,进一步解析
+ $parse = 1;
+ $content[3] = preg_replace_callback($reg, array($this, 'replaceBlock'), "{$content[3]}{$begin}/block{$end}");
+ return $content[1] . $content[3];
+ } else {
+ $name = $content[2];
+ $content = $content[3];
+ $content = isset($this->block[$name]) ? $this->block[$name] : $content;
+ return $content;
+ }
+ }
+ }
+
+ /**
+ * 搜索模板页面中包含的TagLib库
+ * 并返回列表
+ * @access public
+ * @param string $content 模板内容
+ * @return string|false
+ */
+ public function getIncludeTagLib(&$content)
+ {
+ //搜索是否有TagLib标签
+ $find = preg_match('/' . $this->config['taglib_begin'] . 'taglib\s(.+?)(\s*?)\/' . $this->config['taglib_end'] . '\W/is', $content, $matches);
+ if ($find) {
+ //替换TagLib标签
+ $content = str_replace($matches[0], '', $content);
+ //解析TagLib标签
+ $array = $this->parseXmlAttrs($matches[1]);
+ $this->tagLib = explode(',', $array['name']);
+ }
+ return;
+ }
+
+ /**
+ * TagLib库解析
+ * @access public
+ * @param string $tagLib 要解析的标签库
+ * @param string $content 要解析的模板内容
+ * @param boolean $hide 是否隐藏标签库前缀
+ * @return string
+ */
+ public function parseTagLib($tagLib, &$content, $hide = false)
+ {
+ $begin = $this->config['taglib_begin'];
+ $end = $this->config['taglib_end'];
+ if (strpos($tagLib, '\\')) {
+ // 支持指定标签库的命名空间
+ $className = $tagLib;
+ $tagLib = substr($tagLib, strrpos($tagLib, '\\') + 1);
+ } else {
+ $className = 'Think\\Template\TagLib\\' . ucwords($tagLib);
+ }
+ $tLib = \Think\Think::instance($className);
+ $that = $this;
+ foreach ($tLib->getTags() as $name => $val) {
+ $tags = array($name);
+ if (isset($val['alias'])) {
+// 别名设置
+ $tags = explode(',', $val['alias']);
+ $tags[] = $name;
+ }
+ $level = isset($val['level']) ? $val['level'] : 1;
+ $closeTag = isset($val['close']) ? $val['close'] : true;
+ foreach ($tags as $tag) {
+ $parseTag = !$hide ? $tagLib . ':' . $tag : $tag; // 实际要解析的标签名称
+ if (!method_exists($tLib, '_' . $tag)) {
+ // 别名可以无需定义解析方法
+ $tag = $name;
+ }
+ $n1 = empty($val['attr']) ? '(\s*?)' : '\s([^' . $end . ']*)';
+ $this->tempVar = array($tagLib, $tag);
+
+ if (!$closeTag) {
+ $patterns = '/' . $begin . $parseTag . $n1 . '\/(\s*?)' . $end . '/is';
+ $content = preg_replace_callback($patterns, function ($matches) use ($tLib, $tag, $that) {
+ return $that->parseXmlTag($tLib, $tag, $matches[1], $matches[2]);
+ }, $content);
+ } else {
+ $patterns = '/' . $begin . $parseTag . $n1 . $end . '(.*?)' . $begin . '\/' . $parseTag . '(\s*?)' . $end . '/is';
+ for ($i = 0; $i < $level; $i++) {
+ $content = preg_replace_callback($patterns, function ($matches) use ($tLib, $tag, $that) {
+ return $that->parseXmlTag($tLib, $tag, $matches[1], $matches[2]);
+ }, $content);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 解析标签库的标签
+ * 需要调用对应的标签库文件解析类
+ * @access public
+ * @param object $tagLib 标签库对象实例
+ * @param string $tag 标签名
+ * @param string $attr 标签属性
+ * @param string $content 标签内容
+ * @return string|false
+ */
+ public function parseXmlTag($tagLib, $tag, $attr, $content)
+ {
+ if (ini_get('magic_quotes_sybase')) {
+ $attr = str_replace('\"', '\'', $attr);
+ }
+
+ $parse = '_' . $tag;
+ $content = trim($content);
+ $tags = $tagLib->parseXmlAttr($attr, $tag);
+ return $tagLib->$parse($tags, $content);
+ }
+
+ /**
+ * 模板标签解析
+ * 格式: {TagName:args [|content] }
+ * @access public
+ * @param string $tagStr 标签内容
+ * @return string
+ */
+ public function parseTag($tagStr)
+ {
+ if (is_array($tagStr)) {
+ $tagStr = $tagStr[2];
+ }
+
+ //if (MAGIC_QUOTES_GPC) {
+ $tagStr = stripslashes($tagStr);
+ //}
+ $flag = substr($tagStr, 0, 1);
+ $flag2 = substr($tagStr, 1, 1);
+ $name = substr($tagStr, 1);
+ if ('$' == $flag && '.' != $flag2 && '(' != $flag2) {
+ //解析模板变量 格式 {$varName}
+ return $this->parseVar($name);
+ } elseif ('-' == $flag || '+' == $flag) {
+ // 输出计算
+ return '';
+ } elseif (':' == $flag) {
+ // 输出某个函数的结果
+ return '';
+ } elseif ('~' == $flag) {
+ // 执行某个函数
+ return '';
+ } elseif (substr($tagStr, 0, 2) == '//' || (substr($tagStr, 0, 2) == '/*' && substr(rtrim($tagStr), -2) == '*/')) {
+ //注释标签
+ return '';
+ }
+ // 未识别的标签直接返回
+ return C('TMPL_L_DELIM') . $tagStr . C('TMPL_R_DELIM');
+ }
+
+ /**
+ * 模板变量解析,支持使用函数
+ * 格式: {$varname|function1|function2=arg1,arg2}
+ * @access public
+ * @param string $varStr 变量数据
+ * @return string
+ */
+ public function parseVar($varStr)
+ {
+ $varStr = trim($varStr);
+ static $_varParseList = array();
+ //如果已经解析过该变量字串,则直接返回变量值
+ if (isset($_varParseList[$varStr])) {
+ return $_varParseList[$varStr];
+ }
+
+ $parseStr = '';
+ $varExists = true;
+ if (!empty($varStr)) {
+ $varArray = explode('|', $varStr);
+ //取得变量名称
+ $var = array_shift($varArray);
+ if ('Think.' == substr($var, 0, 6)) {
+ // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出
+ $name = $this->parseThinkVar($var);
+ } elseif (false !== strpos($var, '.')) {
+ //支持 {$var.property}
+ $vars = explode('.', $var);
+ $var = array_shift($vars);
+ switch (strtolower(C('TMPL_VAR_IDENTIFY'))) {
+ case 'array': // 识别为数组
+ $name = '$' . $var;
+ foreach ($vars as $key => $val) {
+ $name .= '["' . $val . '"]';
+ }
+
+ break;
+ case 'obj': // 识别为对象
+ $name = '$' . $var;
+ foreach ($vars as $key => $val) {
+ $name .= '->' . $val;
+ }
+
+ break;
+ default: // 自动判断数组或对象 只支持二维
+ $name = 'is_array($' . $var . ')?$' . $var . '["' . $vars[0] . '"]:$' . $var . '->' . $vars[0];
+ }
+ } elseif (false !== strpos($var, '[')) {
+ //支持 {$var['key']} 方式输出数组
+ $name = "$" . $var;
+ preg_match('/(.+?)\[(.+?)\]/is', $var, $match);
+ $var = $match[1];
+ } elseif (false !== strpos($var, ':') && false === strpos($var, '(') && false === strpos($var, '::') && false === strpos($var, '?')) {
+ //支持 {$var:property} 方式输出对象的属性
+ $vars = explode(':', $var);
+ $var = str_replace(':', '->', $var);
+ $name = "$" . $var;
+ $var = $vars[0];
+ } else {
+ $name = "$$var";
+ }
+ //对变量使用函数
+ if (count($varArray) > 0) {
+ $name = $this->parseVarFunction($name, $varArray);
+ }
+
+ $parseStr = '';
+ }
+ $_varParseList[$varStr] = $parseStr;
+ return $parseStr;
+ }
+
+ /**
+ * 对模板变量使用函数
+ * 格式 {$varname|function1|function2=arg1,arg2}
+ * @access public
+ * @param string $name 变量名
+ * @param array $varArray 函数列表
+ * @return string
+ */
+ public function parseVarFunction($name, $varArray)
+ {
+ //对变量使用函数
+ $length = count($varArray);
+ //取得模板禁止使用函数列表
+ $template_deny_funs = explode(',', C('TMPL_DENY_FUNC_LIST'));
+ for ($i = 0; $i < $length; $i++) {
+ $args = explode('=', $varArray[$i], 2);
+ //模板函数过滤
+ $fun = trim($args[0]);
+ switch ($fun) {
+ case 'default': // 特殊模板函数
+ $name = '(isset(' . $name . ') && (' . $name . ' !== ""))?(' . $name . '):' . $args[1];
+ break;
+ default: // 通用模板函数
+ if (!in_array($fun, $template_deny_funs)) {
+ if (isset($args[1])) {
+ if (strstr($args[1], '###')) {
+ $args[1] = str_replace('###', $name, $args[1]);
+ $name = "$fun($args[1])";
+ } else {
+ $name = "$fun($name,$args[1])";
+ }
+ } else if (!empty($args[0])) {
+ $name = "$fun($name)";
+ }
+ }
+ }
+ }
+ return $name;
+ }
+
+ /**
+ * 特殊模板变量解析
+ * 格式 以 $Think. 打头的变量属于特殊模板变量
+ * @access public
+ * @param string $varStr 变量字符串
+ * @return string
+ */
+ public function parseThinkVar($varStr)
+ {
+ $vars = explode('.', $varStr);
+ $vars[1] = strtoupper(trim($vars[1]));
+ $parseStr = '';
+ if (count($vars) >= 3) {
+ $vars[2] = trim($vars[2]);
+ switch ($vars[1]) {
+ case 'SERVER':
+ $parseStr = '$_SERVER[\'' . strtoupper($vars[2]) . '\']';
+ break;
+ case 'GET':
+ $parseStr = '$_GET[\'' . $vars[2] . '\']';
+ break;
+ case 'POST':
+ $parseStr = '$_POST[\'' . $vars[2] . '\']';
+ break;
+ case 'COOKIE':
+ if (isset($vars[3])) {
+ $parseStr = '$_COOKIE[\'' . $vars[2] . '\'][\'' . $vars[3] . '\']';
+ } else {
+ $parseStr = 'cookie(\'' . $vars[2] . '\')';
+ }
+ break;
+ case 'SESSION':
+ if (isset($vars[3])) {
+ $parseStr = '$_SESSION[\'' . $vars[2] . '\'][\'' . $vars[3] . '\']';
+ } else {
+ $parseStr = 'session(\'' . $vars[2] . '\')';
+ }
+ break;
+ case 'ENV':
+ $parseStr = '$_ENV[\'' . strtoupper($vars[2]) . '\']';
+ break;
+ case 'REQUEST':
+ $parseStr = '$_REQUEST[\'' . $vars[2] . '\']';
+ break;
+ case 'CONST':
+ $parseStr = strtoupper($vars[2]);
+ break;
+ case 'LANG':
+ $parseStr = 'L("' . $vars[2] . '")';
+ break;
+ case 'CONFIG':
+ if (isset($vars[3])) {
+ $vars[2] .= '.' . $vars[3];
+ }
+ $parseStr = 'C("' . $vars[2] . '")';
+ break;
+ default:break;
+ }
+ } else if (count($vars) == 2) {
+ switch ($vars[1]) {
+ case 'NOW':
+ $parseStr = "date('Y-m-d g:i a',time())";
+ break;
+ case 'VERSION':
+ $parseStr = 'THINK_VERSION';
+ break;
+ case 'TEMPLATE':
+ $parseStr = "'" . $this->templateFile . "'"; //'C("TEMPLATE_NAME")';
+ break;
+ case 'LDELIM':
+ $parseStr = 'C("TMPL_L_DELIM")';
+ break;
+ case 'RDELIM':
+ $parseStr = 'C("TMPL_R_DELIM")';
+ break;
+ default:
+ if (defined($vars[1])) {
+ $parseStr = $vars[1];
+ }
+
+ }
+ }
+ return $parseStr;
+ }
+
+ /**
+ * 加载公共模板并缓存 和当前模板在同一路径,否则使用相对路径
+ * @access private
+ * @param string $tmplPublicName 公共模板文件名
+ * @param array $vars 要传递的变量列表
+ * @return string
+ */
+ private function parseIncludeItem($tmplPublicName, $vars = array(), $extend)
+ {
+ // 分析模板文件名并读取内容
+ $parseStr = $this->parseTemplateName($tmplPublicName);
+ // 替换变量
+ foreach ($vars as $key => $val) {
+ $parseStr = str_replace('[' . $key . ']', $val, $parseStr);
+ }
+ // 再次对包含文件进行模板分析
+ return $this->parseInclude($parseStr, $extend);
+ }
+
+ /**
+ * 分析加载的模板文件并读取内容 支持多个模板文件读取
+ * @access private
+ * @param string $tmplPublicName 模板文件名
+ * @return string
+ */
+ private function parseTemplateName($templateName)
+ {
+ if (substr($templateName, 0, 1) == '$')
+ //支持加载变量文件名
+ {
+ $templateName = $this->get(substr($templateName, 1));
+ }
+
+ $array = explode(',', $templateName);
+ $parseStr = '';
+ foreach ($array as $templateName) {
+ if (empty($templateName)) {
+ continue;
+ }
+
+ if (false === strpos($templateName, $this->config['template_suffix'])) {
+ // 解析规则为 模块@主题/控制器/操作
+ $templateName = T($templateName);
+ }
+ // 获取模板文件内容
+ $parseStr .= file_get_contents($templateName);
+ }
+ return $parseStr;
+ }
+}
diff --git a/Framework/Library/Think/Template/Driver/Ease.class.php b/Framework/Library/Think/Template/Driver/Ease.class.php
new file mode 100644
index 00000000..b0b4399d
--- /dev/null
+++ b/Framework/Library/Think/Template/Driver/Ease.class.php
@@ -0,0 +1,44 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Template\Driver;
+
+/**
+ * EaseTemplate模板引擎驱动
+ */
+class Ease
+{
+ /**
+ * 渲染模板输出
+ * @access public
+ * @param string $templateFile 模板文件名
+ * @param array $var 模板变量
+ * @return void
+ */
+ public function fetch($templateFile, $var)
+ {
+ $templateFile = substr($templateFile, strlen(THEME_PATH), -5);
+ $CacheDir = substr(CACHE_PATH, 0, -1);
+ $TemplateDir = substr(THEME_PATH, 0, -1);
+ vendor('EaseTemplate.template#ease');
+ $config = array(
+ 'CacheDir' => $CacheDir,
+ 'TemplateDir' => $TemplateDir,
+ 'TplType' => 'html',
+ );
+ if (C('TMPL_ENGINE_CONFIG')) {
+ $config = array_merge($config, C('TMPL_ENGINE_CONFIG'));
+ }
+ $tpl = new \EaseTemplate($config);
+ $tpl->set_var($var);
+ $tpl->set_file($templateFile);
+ $tpl->p();
+ }
+}
diff --git a/Framework/Library/Think/Template/Driver/Lite.class.php b/Framework/Library/Think/Template/Driver/Lite.class.php
new file mode 100644
index 00000000..c7a42a07
--- /dev/null
+++ b/Framework/Library/Think/Template/Driver/Lite.class.php
@@ -0,0 +1,42 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Template\Driver;
+
+/**
+ * TemplateLite模板引擎驱动
+ */
+class Lite
+{
+ /**
+ * 渲染模板输出
+ * @access public
+ * @param string $templateFile 模板文件名
+ * @param array $var 模板变量
+ * @return void
+ */
+ public function fetch($templateFile, $var)
+ {
+ vendor("TemplateLite.class#template");
+ $templateFile = substr($templateFile, strlen(THEME_PATH));
+ $tpl = new \Template_Lite();
+ $tpl->template_dir = THEME_PATH;
+ $tpl->compile_dir = CACHE_PATH;
+ $tpl->cache_dir = TEMP_PATH;
+ if (C('TMPL_ENGINE_CONFIG')) {
+ $config = C('TMPL_ENGINE_CONFIG');
+ foreach ($config as $key => $val) {
+ $tpl->{$key} = $val;
+ }
+ }
+ $tpl->assign($var);
+ $tpl->display($templateFile);
+ }
+}
diff --git a/Framework/Library/Think/Template/Driver/Mobile.class.php b/Framework/Library/Think/Template/Driver/Mobile.class.php
new file mode 100644
index 00000000..67154961
--- /dev/null
+++ b/Framework/Library/Think/Template/Driver/Mobile.class.php
@@ -0,0 +1,31 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Template\Driver;
+
+/**
+ * MobileTemplate模板引擎驱动
+ */
+class Mobile
+{
+ /**
+ * 渲染模板输出
+ * @access public
+ * @param string $templateFile 模板文件名
+ * @param array $var 模板变量
+ * @return void
+ */
+ public function fetch($templateFile, $var)
+ {
+ $templateFile = substr($templateFile, strlen(THEME_PATH));
+ $var['_think_template_path'] = $templateFile;
+ exit(json_encode($var));
+ }
+}
diff --git a/Framework/Library/Think/Template/Driver/Smart.class.php b/Framework/Library/Think/Template/Driver/Smart.class.php
new file mode 100644
index 00000000..c830f456
--- /dev/null
+++ b/Framework/Library/Think/Template/Driver/Smart.class.php
@@ -0,0 +1,43 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Template\Driver;
+
+/**
+ * Smart模板引擎驱动
+ */
+class Smart
+{
+ /**
+ * 渲染模板输出
+ * @access public
+ * @param string $templateFile 模板文件名
+ * @param array $var 模板变量
+ * @return void
+ */
+ public function fetch($templateFile, $var)
+ {
+ $templateFile = substr($templateFile, strlen(THEME_PATH));
+ vendor('SmartTemplate.class#smarttemplate');
+ $tpl = new \SmartTemplate($templateFile);
+ $tpl->caching = C('TMPL_CACHE_ON');
+ $tpl->template_dir = THEME_PATH;
+ $tpl->compile_dir = CACHE_PATH;
+ $tpl->cache_dir = TEMP_PATH;
+ if (C('TMPL_ENGINE_CONFIG')) {
+ $config = C('TMPL_ENGINE_CONFIG');
+ foreach ($config as $key => $val) {
+ $tpl->{$key} = $val;
+ }
+ }
+ $tpl->assign($var);
+ $tpl->output();
+ }
+}
diff --git a/Framework/Library/Think/Template/Driver/Smarty.class.php b/Framework/Library/Think/Template/Driver/Smarty.class.php
new file mode 100644
index 00000000..a6fa823c
--- /dev/null
+++ b/Framework/Library/Think/Template/Driver/Smarty.class.php
@@ -0,0 +1,44 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Template\Driver;
+
+/**
+ * Smarty模板引擎驱动
+ */
+class Smarty
+{
+
+ /**
+ * 渲染模板输出
+ * @access public
+ * @param string $templateFile 模板文件名
+ * @param array $var 模板变量
+ * @return void
+ */
+ public function fetch($templateFile, $var)
+ {
+ $templateFile = substr($templateFile, strlen(THEME_PATH));
+ vendor('Smarty.Smarty#class');
+ $tpl = new \Smarty();
+ $tpl->caching = C('TMPL_CACHE_ON');
+ $tpl->template_dir = THEME_PATH;
+ $tpl->compile_dir = CACHE_PATH;
+ $tpl->cache_dir = TEMP_PATH;
+ if (C('TMPL_ENGINE_CONFIG')) {
+ $config = C('TMPL_ENGINE_CONFIG');
+ foreach ($config as $key => $val) {
+ $tpl->{$key} = $val;
+ }
+ }
+ $tpl->assign($var);
+ $tpl->display($templateFile);
+ }
+}
diff --git a/Framework/Library/Think/Template/TagLib.class.php b/Framework/Library/Think/Template/TagLib.class.php
new file mode 100644
index 00000000..1426212c
--- /dev/null
+++ b/Framework/Library/Think/Template/TagLib.class.php
@@ -0,0 +1,275 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Template;
+
+/**
+ * ThinkPHP标签库TagLib解析基类
+ */
+class TagLib
+{
+
+ /**
+ * 标签库定义XML文件
+ * @var string
+ * @access protected
+ */
+ protected $xml = '';
+ protected $tags = array(); // 标签定义
+ /**
+ * 标签库名称
+ * @var string
+ * @access protected
+ */
+ protected $tagLib = '';
+
+ /**
+ * 标签库标签列表
+ * @var string
+ * @access protected
+ */
+ protected $tagList = array();
+
+ /**
+ * 标签库分析数组
+ * @var string
+ * @access protected
+ */
+ protected $parse = array();
+
+ /**
+ * 标签库是否有效
+ * @var string
+ * @access protected
+ */
+ protected $valid = false;
+
+ /**
+ * 当前模板对象
+ * @var object
+ * @access protected
+ */
+ protected $tpl;
+
+ protected $comparison = array(' nheq ' => ' !== ', ' heq ' => ' === ', ' neq ' => ' != ', ' eq ' => ' == ', ' egt ' => ' >= ', ' gt ' => ' > ', ' elt ' => ' <= ', ' lt ' => ' < ');
+
+ /**
+ * 架构函数
+ * @access public
+ */
+ public function __construct()
+ {
+ $this->tagLib = strtolower(substr(get_class($this), 6));
+ $this->tpl = \Think\Think::instance('Think\\Template');
+ }
+
+ /**
+ * TagLib标签属性分析 返回标签属性数组
+ * @access public
+ * @param string $tagStr 标签内容
+ * @return array
+ */
+ public function parseXmlAttr($attr, $tag)
+ {
+ //XML解析安全过滤
+ $attr = str_replace('&', '___', $attr);
+ $xml = ' ';
+ $xml = simplexml_load_string($xml);
+ if (!$xml) {
+ E(L('_XML_TAG_ERROR_') . ' : ' . $attr);
+ }
+ $xml = (array) ($xml->tag->attributes());
+ if (isset($xml['@attributes'])) {
+ $array = array_change_key_case($xml['@attributes']);
+ if ($array) {
+ $tag = strtolower($tag);
+ if (!isset($this->tags[$tag])) {
+ // 检测是否存在别名定义
+ foreach ($this->tags as $key => $val) {
+ if (isset($val['alias']) && in_array($tag, explode(',', $val['alias']))) {
+ $item = $val;
+ break;
+ }
+ }
+ } else {
+ $item = $this->tags[$tag];
+ }
+ $attrs = explode(',', $item['attr']);
+ if (isset($item['must'])) {
+ $must = explode(',', $item['must']);
+ } else {
+ $must = array();
+ }
+ foreach ($attrs as $name) {
+ if (isset($array[$name])) {
+ $array[$name] = str_replace('___', '&', $array[$name]);
+ } elseif (false !== array_search($name, $must)) {
+ E(L('_PARAM_ERROR_') . ':' . $name);
+ }
+ }
+ return $array;
+ }
+ } else {
+ return array();
+ }
+ }
+
+ /**
+ * 解析条件表达式
+ * @access public
+ * @param string $condition 表达式标签内容
+ * @return array
+ */
+ public function parseCondition($condition)
+ {
+ $condition = str_ireplace(array_keys($this->comparison), array_values($this->comparison), $condition);
+ $condition = preg_replace('/\$(\w+):(\w+)\s/is', '$\\1->\\2 ', $condition);
+ switch (strtolower(C('TMPL_VAR_IDENTIFY'))) {
+ case 'array': // 识别为数组
+ $condition = preg_replace('/\$(\w+)\.(\w+)\s/is', '$\\1["\\2"] ', $condition);
+ break;
+ case 'obj': // 识别为对象
+ $condition = preg_replace('/\$(\w+)\.(\w+)\s/is', '$\\1->\\2 ', $condition);
+ break;
+ default: // 自动判断数组或对象 只支持二维
+ $condition = preg_replace('/\$(\w+)\.(\w+)\s/is', '(is_array($\\1)?$\\1["\\2"]:$\\1->\\2) ', $condition);
+ }
+ if (false !== strpos($condition, '$Think')) {
+ $condition = preg_replace_callback('/(\$Think.*?)\s/is', array($this, 'parseThinkVar'), $condition);
+ }
+
+ return $condition;
+ }
+
+ /**
+ * 自动识别构建变量
+ * @access public
+ * @param string $name 变量描述
+ * @return string
+ */
+ public function autoBuildVar($name)
+ {
+ if ('Think.' == substr($name, 0, 6)) {
+ // 特殊变量
+ return $this->parseThinkVar($name);
+ } elseif (strpos($name, '.')) {
+ $vars = explode('.', $name);
+ $var = array_shift($vars);
+ switch (strtolower(C('TMPL_VAR_IDENTIFY'))) {
+ case 'array': // 识别为数组
+ $name = '$' . $var;
+ foreach ($vars as $key => $val) {
+ if (0 === strpos($val, '$')) {
+ $name .= '["{' . $val . '}"]';
+ } else {
+ $name .= '["' . $val . '"]';
+ }
+ }
+ break;
+ case 'obj': // 识别为对象
+ $name = '$' . $var;
+ foreach ($vars as $key => $val) {
+ $name .= '->' . $val;
+ }
+
+ break;
+ default: // 自动判断数组或对象 只支持二维
+ $name = 'is_array($' . $var . ')?$' . $var . '["' . $vars[0] . '"]:$' . $var . '->' . $vars[0];
+ }
+ } elseif (strpos($name, ':')) {
+ // 额外的对象方式支持
+ $name = '$' . str_replace(':', '->', $name);
+ } elseif (!defined($name)) {
+ $name = '$' . $name;
+ }
+ return $name;
+ }
+
+ /**
+ * 用于标签属性里面的特殊模板变量解析
+ * 格式 以 Think. 打头的变量属于特殊模板变量
+ * @access public
+ * @param string $varStr 变量字符串
+ * @return string
+ */
+ public function parseThinkVar($varStr)
+ {
+ if (is_array($varStr)) {
+//用于正则替换回调函数
+ $varStr = $varStr[1];
+ }
+ $vars = explode('.', $varStr);
+ $vars[1] = strtoupper(trim($vars[1]));
+ $parseStr = '';
+ if (count($vars) >= 3) {
+ $vars[2] = trim($vars[2]);
+ switch ($vars[1]) {
+ case 'SERVER':$parseStr = '$_SERVER[\'' . $vars[2] . '\']';
+ break;
+ case 'GET':$parseStr = '$_GET[\'' . $vars[2] . '\']';
+ break;
+ case 'POST':$parseStr = '$_POST[\'' . $vars[2] . '\']';
+ break;
+ case 'COOKIE':
+ if (isset($vars[3])) {
+ $parseStr = '$_COOKIE[\'' . $vars[2] . '\'][\'' . $vars[3] . '\']';
+ } elseif (C('COOKIE_PREFIX')) {
+ $parseStr = '$_COOKIE[\'' . C('COOKIE_PREFIX') . $vars[2] . '\']';
+ } else {
+ $parseStr = '$_COOKIE[\'' . $vars[2] . '\']';
+ }
+ break;
+ case 'SESSION':
+ if (isset($vars[3])) {
+ $parseStr = '$_SESSION[\'' . $vars[2] . '\'][\'' . $vars[3] . '\']';
+ } elseif (C('SESSION_PREFIX')) {
+ $parseStr = '$_SESSION[\'' . C('SESSION_PREFIX') . '\'][\'' . $vars[2] . '\']';
+ } else {
+ $parseStr = '$_SESSION[\'' . $vars[2] . '\']';
+ }
+ break;
+ case 'ENV':$parseStr = '$_ENV[\'' . $vars[2] . '\']';
+ break;
+ case 'REQUEST':$parseStr = '$_REQUEST[\'' . $vars[2] . '\']';
+ break;
+ case 'CONST':$parseStr = strtoupper($vars[2]);
+ break;
+ case 'LANG':$parseStr = 'L("' . $vars[2] . '")';
+ break;
+ case 'CONFIG':$parseStr = 'C("' . $vars[2] . '")';
+ break;
+ }
+ } else if (count($vars) == 2) {
+ switch ($vars[1]) {
+ case 'NOW':$parseStr = "date('Y-m-d g:i a',time())";
+ break;
+ case 'VERSION':$parseStr = 'THINK_VERSION';
+ break;
+ case 'TEMPLATE':$parseStr = 'C("TEMPLATE_NAME")';
+ break;
+ case 'LDELIM':$parseStr = 'C("TMPL_L_DELIM")';
+ break;
+ case 'RDELIM':$parseStr = 'C("TMPL_R_DELIM")';
+ break;
+ default:if (defined($vars[1])) {
+ $parseStr = $vars[1];
+ }
+
+ }
+ }
+ return $parseStr;
+ }
+
+ // 获取标签定义
+ public function getTags()
+ {
+ return $this->tags;
+ }
+}
diff --git a/Framework/Library/Think/Template/TagLib/Cx.class.php b/Framework/Library/Think/Template/TagLib/Cx.class.php
new file mode 100644
index 00000000..901e20c4
--- /dev/null
+++ b/Framework/Library/Think/Template/TagLib/Cx.class.php
@@ -0,0 +1,671 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Template\TagLib;
+
+use Think\Template\TagLib;
+
+/**
+ * CX标签库解析类
+ */
+class Cx extends TagLib
+{
+
+ // 标签定义
+ protected $tags = array(
+ // 标签定义: attr 属性列表 close 是否闭合(0 或者1 默认1) alias 标签别名 level 嵌套层次
+ 'php' => array(),
+ 'volist' => array('attr' => 'name,id,offset,length,key,mod', 'level' => 3, 'alias' => 'iterate'),
+ 'foreach' => array('attr' => 'name,item,key', 'level' => 3),
+ 'if' => array('attr' => 'condition', 'level' => 2),
+ 'elseif' => array('attr' => 'condition', 'close' => 0),
+ 'else' => array('attr' => '', 'close' => 0),
+ 'switch' => array('attr' => 'name', 'level' => 2),
+ 'case' => array('attr' => 'value,break'),
+ 'default' => array('attr' => '', 'close' => 0),
+ 'compare' => array('attr' => 'name,value,type', 'level' => 3, 'alias' => 'eq,equal,notequal,neq,gt,lt,egt,elt,heq,nheq'),
+ 'range' => array('attr' => 'name,value,type', 'level' => 3, 'alias' => 'in,notin,between,notbetween'),
+ 'empty' => array('attr' => 'name', 'level' => 3),
+ 'notempty' => array('attr' => 'name', 'level' => 3),
+ 'present' => array('attr' => 'name', 'level' => 3),
+ 'notpresent' => array('attr' => 'name', 'level' => 3),
+ 'defined' => array('attr' => 'name', 'level' => 3),
+ 'notdefined' => array('attr' => 'name', 'level' => 3),
+ 'import' => array('attr' => 'file,href,type,value,basepath', 'close' => 0, 'alias' => 'load,css,js'),
+ 'assign' => array('attr' => 'name,value', 'close' => 0),
+ 'define' => array('attr' => 'name,value', 'close' => 0),
+ 'for' => array('attr' => 'start,end,name,comparison,step', 'level' => 3),
+ );
+
+ /**
+ * php标签解析
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function _php($tag, $content)
+ {
+ $parseStr = '';
+ return $parseStr;
+ }
+
+ /**
+ * volist标签解析 循环输出数据集
+ * 格式:
+ *
+ * {user.username}
+ * {user.email}
+ *
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string|void
+ */
+ public function _volist($tag, $content)
+ {
+ $name = $tag['name'];
+ $id = $tag['id'];
+ $empty = isset($tag['empty']) ? $tag['empty'] : '';
+ $key = !empty($tag['key']) ? $tag['key'] : 'i';
+ $mod = isset($tag['mod']) ? $tag['mod'] : '2';
+ // 允许使用函数设定数据集 {$vo.name}
+ $parseStr = 'autoBuildVar($name);
+ }
+ $parseStr .= 'if(is_array(' . $name . ')): $' . $key . ' = 0;';
+ if (isset($tag['length']) && '' != $tag['length']) {
+ $parseStr .= ' $__LIST__ = array_slice(' . $name . ',' . $tag['offset'] . ',' . $tag['length'] . ',true);';
+ } elseif (isset($tag['offset']) && '' != $tag['offset']) {
+ $parseStr .= ' $__LIST__ = array_slice(' . $name . ',' . $tag['offset'] . ',null,true);';
+ } else {
+ $parseStr .= ' $__LIST__ = ' . $name . ';';
+ }
+ $parseStr .= 'if( count($__LIST__)==0 ) : echo "' . $empty . '" ;';
+ $parseStr .= 'else: ';
+ $parseStr .= 'foreach($__LIST__ as $key=>$' . $id . '): ';
+ $parseStr .= '$mod = ($' . $key . ' % ' . $mod . ' );';
+ $parseStr .= '++$' . $key . ';?>';
+ $parseStr .= $this->tpl->parse($content);
+ $parseStr .= '';
+
+ if (!empty($parseStr)) {
+ return $parseStr;
+ }
+ return;
+ }
+
+ /**
+ * foreach标签解析 循环输出数据集
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string|void
+ */
+ public function _foreach($tag, $content)
+ {
+ $name = $tag['name'];
+ $item = $tag['item'];
+ $key = !empty($tag['key']) ? $tag['key'] : 'key';
+ $name = $this->autoBuildVar($name);
+ $parseStr = '$' . $item . '): ?>';
+ $parseStr .= $this->tpl->parse($content);
+ $parseStr .= '';
+
+ if (!empty($parseStr)) {
+ return $parseStr;
+ }
+ return;
+ }
+
+ /**
+ * if标签解析
+ * 格式:
+ *
+ *
+ *
+ *
+ * 表达式支持 eq neq gt egt lt elt == > >= < <= or and || &&
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function _if($tag, $content)
+ {
+ $condition = $this->parseCondition($tag['condition']);
+ $parseStr = '' . $content . '';
+ return $parseStr;
+ }
+
+ /**
+ * else标签解析
+ * 格式:见if标签
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function _elseif($tag, $content)
+ {
+ $condition = $this->parseCondition($tag['condition']);
+ $parseStr = '';
+ return $parseStr;
+ }
+
+ /**
+ * else标签解析
+ * @access public
+ * @param array $tag 标签属性
+ * @return string
+ */
+ public function _else($tag)
+ {
+ $parseStr = '';
+ return $parseStr;
+ }
+
+ /**
+ * switch标签解析
+ * 格式:
+ *
+ * 1
+ * 2
+ * other
+ *
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function _switch($tag, $content)
+ {
+ $name = $tag['name'];
+ $varArray = explode('|', $name);
+ $name = array_shift($varArray);
+ $name = $this->autoBuildVar($name);
+ if (count($varArray) > 0) {
+ $name = $this->tpl->parseVarFunction($name, $varArray);
+ }
+
+ $parseStr = '' . $content . '';
+ return $parseStr;
+ }
+
+ /**
+ * case标签解析 需要配合switch才有效
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function _case($tag, $content)
+ {
+ $value = $tag['value'];
+ if ('$' == substr($value, 0, 1)) {
+ $varArray = explode('|', $value);
+ $value = array_shift($varArray);
+ $value = $this->autoBuildVar(substr($value, 1));
+ if (count($varArray) > 0) {
+ $value = $this->tpl->parseVarFunction($value, $varArray);
+ }
+
+ $value = 'case ' . $value . ': ';
+ } elseif (strpos($value, '|')) {
+ $values = explode('|', $value);
+ $value = '';
+ foreach ($values as $val) {
+ $value .= 'case "' . addslashes($val) . '": ';
+ }
+ } else {
+ $value = 'case "' . $value . '": ';
+ }
+ $parseStr = '' . $content;
+ $isBreak = isset($tag['break']) ? $tag['break'] : '';
+ if ('' == $isBreak || $isBreak) {
+ $parseStr .= '';
+ }
+ return $parseStr;
+ }
+
+ /**
+ * default标签解析 需要配合switch才有效
+ * 使用: ddfdf
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function _default($tag)
+ {
+ $parseStr = '';
+ return $parseStr;
+ }
+
+ /**
+ * compare标签解析
+ * 用于值的比较 支持 eq neq gt lt egt elt heq nheq 默认是eq
+ * 格式: content
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function _compare($tag, $content, $type = 'eq')
+ {
+ $name = $tag['name'];
+ $value = $tag['value'];
+ $type = isset($tag['type']) ? $tag['type'] : $type;
+ $type = $this->parseCondition(' ' . $type . ' ');
+ $varArray = explode('|', $name);
+ $name = array_shift($varArray);
+ $name = $this->autoBuildVar($name);
+ if (count($varArray) > 0) {
+ $name = $this->tpl->parseVarFunction($name, $varArray);
+ }
+
+ if ('$' == substr($value, 0, 1)) {
+ $value = $this->autoBuildVar(substr($value, 1));
+ } else {
+ $value = '"' . $value . '"';
+ }
+ $parseStr = '' . $content . '';
+ return $parseStr;
+ }
+
+ public function _eq($tag, $content)
+ {
+ return $this->_compare($tag, $content, 'eq');
+ }
+
+ public function _equal($tag, $content)
+ {
+ return $this->_compare($tag, $content, 'eq');
+ }
+
+ public function _neq($tag, $content)
+ {
+ return $this->_compare($tag, $content, 'neq');
+ }
+
+ public function _notequal($tag, $content)
+ {
+ return $this->_compare($tag, $content, 'neq');
+ }
+
+ public function _gt($tag, $content)
+ {
+ return $this->_compare($tag, $content, 'gt');
+ }
+
+ public function _lt($tag, $content)
+ {
+ return $this->_compare($tag, $content, 'lt');
+ }
+
+ public function _egt($tag, $content)
+ {
+ return $this->_compare($tag, $content, 'egt');
+ }
+
+ public function _elt($tag, $content)
+ {
+ return $this->_compare($tag, $content, 'elt');
+ }
+
+ public function _heq($tag, $content)
+ {
+ return $this->_compare($tag, $content, 'heq');
+ }
+
+ public function _nheq($tag, $content)
+ {
+ return $this->_compare($tag, $content, 'nheq');
+ }
+
+ /**
+ * range标签解析
+ * 如果某个变量存在于某个范围 则输出内容 type= in 表示在范围内 否则表示在范围外
+ * 格式: content
+ * example: content
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @param string $type 比较类型
+ * @return string
+ */
+ public function _range($tag, $content, $type = 'in')
+ {
+ $name = $tag['name'];
+ $value = $tag['value'];
+ $varArray = explode('|', $name);
+ $name = array_shift($varArray);
+ $name = $this->autoBuildVar($name);
+ if (count($varArray) > 0) {
+ $name = $this->tpl->parseVarFunction($name, $varArray);
+ }
+
+ $type = isset($tag['type']) ? $tag['type'] : $type;
+
+ if ('$' == substr($value, 0, 1)) {
+ $value = $this->autoBuildVar(substr($value, 1));
+ $str = 'is_array(' . $value . ')?' . $value . ':explode(\',\',' . $value . ')';
+ } else {
+ $value = '"' . $value . '"';
+ $str = 'explode(\',\',' . $value . ')';
+ }
+ if ('between' == $type) {
+ $parseStr = '= $_RANGE_VAR_[0] && ' . $name . '<= $_RANGE_VAR_[1]):?>' . $content . '';
+ } elseif ('notbetween' == $type) {
+ $parseStr = '$_RANGE_VAR_[1]):?>' . $content . '';
+ } else {
+ $fun = ('in' == $type) ? 'in_array' : '!in_array';
+ $parseStr = '' . $content . '';
+ }
+ return $parseStr;
+ }
+
+ // range标签的别名 用于in判断
+ public function _in($tag, $content)
+ {
+ return $this->_range($tag, $content, 'in');
+ }
+
+ // range标签的别名 用于notin判断
+ public function _notin($tag, $content)
+ {
+ return $this->_range($tag, $content, 'notin');
+ }
+
+ public function _between($tag, $content)
+ {
+ return $this->_range($tag, $content, 'between');
+ }
+
+ public function _notbetween($tag, $content)
+ {
+ return $this->_range($tag, $content, 'notbetween');
+ }
+
+ /**
+ * present标签解析
+ * 如果某个变量已经设置 则输出内容
+ * 格式: content
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function _present($tag, $content)
+ {
+ $name = $tag['name'];
+ $name = $this->autoBuildVar($name);
+ $parseStr = '' . $content . '';
+ return $parseStr;
+ }
+
+ /**
+ * notpresent标签解析
+ * 如果某个变量没有设置,则输出内容
+ * 格式: content
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function _notpresent($tag, $content)
+ {
+ $name = $tag['name'];
+ $name = $this->autoBuildVar($name);
+ $parseStr = '' . $content . '';
+ return $parseStr;
+ }
+
+ /**
+ * empty标签解析
+ * 如果某个变量为empty 则输出内容
+ * 格式: content
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function _empty($tag, $content)
+ {
+ $name = $tag['name'];
+ $name = $this->autoBuildVar($name);
+ $parseStr = '' . $content . '';
+ return $parseStr;
+ }
+
+ public function _notempty($tag, $content)
+ {
+ $name = $tag['name'];
+ $name = $this->autoBuildVar($name);
+ $parseStr = '' . $content . '';
+ return $parseStr;
+ }
+
+ /**
+ * 判断是否已经定义了该常量
+ * 已定义
+ * @param $attr
+ * @param $content
+ * @return string
+ */
+ public function _defined($tag, $content)
+ {
+ $name = $tag['name'];
+ $parseStr = '' . $content . '';
+ return $parseStr;
+ }
+
+ public function _notdefined($tag, $content)
+ {
+ $name = $tag['name'];
+ $parseStr = '' . $content . '';
+ return $parseStr;
+ }
+
+ /**
+ * import 标签解析
+ *
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @param boolean $isFile 是否文件方式
+ * @param string $type 类型
+ * @return string
+ */
+ public function _import($tag, $content, $isFile = false, $type = '')
+ {
+ $file = isset($tag['file']) ? $tag['file'] : $tag['href'];
+ $parseStr = '';
+ $endStr = '';
+ // 判断是否存在加载条件 允许使用函数判断(默认为isset)
+ if (isset($tag['value'])) {
+ $varArray = explode('|', $tag['value']);
+ $name = array_shift($varArray);
+ $name = $this->autoBuildVar($name);
+ if (!empty($varArray)) {
+ $name = $this->tpl->parseVarFunction($name, $varArray);
+ } else {
+ $name = 'isset(' . $name . ')';
+ }
+
+ $parseStr .= '';
+ $endStr = '';
+ }
+ if ($isFile) {
+ // 根据文件名后缀自动识别
+ $type = $type ? $type : (!empty($tag['type']) ? strtolower($tag['type']) : null);
+ // 文件方式导入
+ $array = explode(',', $file);
+ foreach ($array as $val) {
+ if (!$type || isset($reset)) {
+ $type = $reset = strtolower(substr(strrchr($val, '.'), 1));
+ }
+ switch ($type) {
+ case 'js':
+ $parseStr .= '';
+ break;
+ case 'css':
+ $parseStr .= ' ';
+ break;
+ case 'php':
+ $parseStr .= '';
+ break;
+ }
+ }
+ } else {
+ // 命名空间导入模式 默认是js
+ $type = $type ? $type : (!empty($tag['type']) ? strtolower($tag['type']) : 'js');
+ $basepath = !empty($tag['basepath']) ? $tag['basepath'] : __ROOT__ . '/Public';
+ // 命名空间方式导入外部文件
+ $array = explode(',', $file);
+ foreach ($array as $val) {
+ if (strpos($val, '?')) {
+ list($val, $version) = explode('?', $val);
+ } else {
+ $version = '';
+ }
+ switch ($type) {
+ case 'js':
+ $parseStr .= '';
+ break;
+ case 'css':
+ $parseStr .= ' ';
+ break;
+ case 'php':
+ $parseStr .= '';
+ break;
+ }
+ }
+ }
+ return $parseStr . $endStr;
+ }
+
+ // import别名 采用文件方式加载(要使用命名空间必须用import) 例如
+ public function _load($tag, $content)
+ {
+ return $this->_import($tag, $content, true);
+ }
+
+ // import别名使用 导入css文件
+ public function _css($tag, $content)
+ {
+ return $this->_import($tag, $content, true, 'css');
+ }
+
+ // import别名使用 导入js文件
+ public function _js($tag, $content)
+ {
+ return $this->_import($tag, $content, true, 'js');
+ }
+
+ /**
+ * assign标签解析
+ * 在模板中给某个变量赋值 支持变量赋值
+ * 格式:
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function _assign($tag, $content)
+ {
+ $name = $this->autoBuildVar($tag['name']);
+ if ('$' == substr($tag['value'], 0, 1)) {
+ $value = $this->autoBuildVar(substr($tag['value'], 1));
+ } else {
+ $value = '\'' . $tag['value'] . '\'';
+ }
+ $parseStr = '';
+ return $parseStr;
+ }
+
+ /**
+ * define标签解析
+ * 在模板中定义常量 支持变量赋值
+ * 格式:
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function _define($tag, $content)
+ {
+ $name = '\'' . $tag['name'] . '\'';
+ if ('$' == substr($tag['value'], 0, 1)) {
+ $value = $this->autoBuildVar(substr($tag['value'], 1));
+ } else {
+ $value = '\'' . $tag['value'] . '\'';
+ }
+ $parseStr = '';
+ return $parseStr;
+ }
+
+ /**
+ * for标签解析
+ * 格式:
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function _for($tag, $content)
+ {
+ //设置默认值
+ $start = 0;
+ $end = 0;
+ $step = 1;
+ $comparison = 'lt';
+ $name = 'i';
+ $rand = rand(); //添加随机数,防止嵌套变量冲突
+ //获取属性
+ foreach ($tag as $key => $value) {
+ $value = trim($value);
+ if (':' == substr($value, 0, 1)) {
+ $value = substr($value, 1);
+ } elseif ('$' == substr($value, 0, 1)) {
+ $value = $this->autoBuildVar(substr($value, 1));
+ }
+
+ switch ($key) {
+ case 'start':
+ $start = $value;
+ break;
+ case 'end':
+ $end = $value;
+ break;
+ case 'step':
+ $step = $value;
+ break;
+ case 'comparison':
+ $comparison = $value;
+ break;
+ case 'name':
+ $name = $value;
+ break;
+ }
+ }
+
+ $parseStr = 'parseCondition('$' . $name . ' ' . $comparison . ' $__FOR_END_' . $rand . '__') . ';$' . $name . '+=' . $step . '){ ?>';
+ $parseStr .= $content;
+ $parseStr .= '';
+ return $parseStr;
+ }
+
+}
diff --git a/Framework/Library/Think/Template/TagLib/Html.class.php b/Framework/Library/Think/Template/TagLib/Html.class.php
new file mode 100644
index 00000000..cbcf2c78
--- /dev/null
+++ b/Framework/Library/Think/Template/TagLib/Html.class.php
@@ -0,0 +1,555 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Template\TagLib;
+
+use Think\Template\TagLib;
+
+/**
+ * Html标签库驱动
+ */
+class Html extends TagLib
+{
+ // 标签定义
+ protected $tags = array(
+ // 标签定义: attr 属性列表 close 是否闭合(0 或者1 默认1) alias 标签别名 level 嵌套层次
+ 'editor' => array('attr' => 'id,name,style,width,height,type', 'close' => 1),
+ 'select' => array('attr' => 'name,options,values,output,multiple,id,size,first,change,selected,dblclick', 'close' => 0),
+ 'grid' => array('attr' => 'id,pk,style,action,actionlist,show,datasource', 'close' => 0),
+ 'list' => array('attr' => 'id,pk,style,action,actionlist,show,datasource,checkbox', 'close' => 0),
+ 'imagebtn' => array('attr' => 'id,name,value,type,style,click', 'close' => 0),
+ 'checkbox' => array('attr' => 'name,checkboxes,checked,separator', 'close' => 0),
+ 'radio' => array('attr' => 'name,radios,checked,separator', 'close' => 0),
+ );
+
+ /**
+ * editor标签解析 插入可视化编辑器
+ * 格式: {$vo.remark}
+ * @access public
+ * @param array $tag 标签属性
+ * @return string|void
+ */
+ public function _editor($tag, $content)
+ {
+ $id = !empty($tag['id']) ? $tag['id'] : '_editor';
+ $name = $tag['name'];
+ $style = !empty($tag['style']) ? $tag['style'] : '';
+ $width = !empty($tag['width']) ? $tag['width'] : '100%';
+ $height = !empty($tag['height']) ? $tag['height'] : '320px';
+ // $content = $tag['content'];
+ $type = $tag['type'];
+ switch (strtoupper($type)) {
+ case 'FCKEDITOR':
+ $parseStr = ' ';
+ break;
+ case 'FCKMINI':
+ $parseStr = ' ';
+ break;
+ case 'EWEBEDITOR':
+ $parseStr = " ";
+ break;
+ case 'NETEASE':
+ $parseStr = '';
+ break;
+ case 'UBB':
+ $parseStr = '
';
+ break;
+ case 'KINDEDITOR':
+ $parseStr = '';
+ break;
+ default:
+ $parseStr = '';
+ }
+
+ return $parseStr;
+ }
+
+ /**
+ * imageBtn标签解析
+ * 格式:
+ * @access public
+ * @param array $tag 标签属性
+ * @return string|void
+ */
+ public function _imageBtn($tag)
+ {
+ $name = $tag['name']; //名称
+ $value = $tag['value']; //文字
+ $id = isset($tag['id']) ? $tag['id'] : ''; //ID
+ $style = isset($tag['style']) ? $tag['style'] : ''; //样式名
+ $click = isset($tag['click']) ? $tag['click'] : ''; //点击
+ $type = empty($tag['type']) ? 'button' : $tag['type']; //按钮类型
+
+ if (!empty($name)) {
+ $parseStr = '
';
+ } else {
+ $parseStr = '
';
+ }
+
+ return $parseStr;
+ }
+
+ /**
+ * imageLink标签解析
+ * 格式:
+ * @access public
+ * @param array $tag 标签属性
+ * @return string|void
+ */
+ public function _imgLink($tag)
+ {
+ $name = $tag['name']; //名称
+ $alt = $tag['alt']; //文字
+ $id = $tag['id']; //ID
+ $style = $tag['style']; //样式名
+ $click = $tag['click']; //点击
+ $type = $tag['type']; //点击
+ if (empty($type)) {
+ $type = 'button';
+ }
+ $parseStr = ' ';
+
+ return $parseStr;
+ }
+
+ /**
+ * select标签解析
+ * 格式:
+ * @access public
+ * @param array $tag 标签属性
+ * @return string|void
+ */
+ public function _select($tag)
+ {
+ $name = $tag['name'];
+ $options = $tag['options'];
+ $values = $tag['values'];
+ $output = $tag['output'];
+ $multiple = $tag['multiple'];
+ $id = $tag['id'];
+ $size = $tag['size'];
+ $first = $tag['first'];
+ $selected = $tag['selected'];
+ $style = $tag['style'];
+ $ondblclick = $tag['dblclick'];
+ $onchange = $tag['change'];
+
+ if (!empty($multiple)) {
+ $parseStr = '';
+ } else {
+ $parseStr = '';
+ }
+ if (!empty($first)) {
+ $parseStr .= '' . $first . ' ';
+ }
+ if (!empty($options)) {
+ $parseStr .= '$val) { ?>';
+ if (!empty($selected)) {
+ $parseStr .= '';
+ $parseStr .= ' ';
+ $parseStr .= ' ';
+ $parseStr .= '';
+ } else {
+ $parseStr .= ' ';
+ }
+ $parseStr .= '';
+ } else if (!empty($values)) {
+ $parseStr .= '';
+ if (!empty($selected)) {
+ $parseStr .= '';
+ $parseStr .= ' ';
+ $parseStr .= ' ';
+ $parseStr .= '';
+ } else {
+ $parseStr .= ' ';
+ }
+ $parseStr .= '';
+ }
+ $parseStr .= ' ';
+ return $parseStr;
+ }
+
+ /**
+ * checkbox标签解析
+ * 格式:
+ * @access public
+ * @param array $tag 标签属性
+ * @return string|void
+ */
+ public function _checkbox($tag)
+ {
+ $name = $tag['name'];
+ $checkboxes = $tag['checkboxes'];
+ $checked = $tag['checked'];
+ $separator = $tag['separator'];
+ $checkboxes = $this->tpl->get($checkboxes);
+ $checked = $this->tpl->get($checked) ? $this->tpl->get($checked) : $checked;
+ $parseStr = '';
+ foreach ($checkboxes as $key => $val) {
+ if ($checked == $key || in_array($key, $checked)) {
+ $parseStr .= ' ' . $val . $separator;
+ } else {
+ $parseStr .= ' ' . $val . $separator;
+ }
+ }
+ return $parseStr;
+ }
+
+ /**
+ * radio标签解析
+ * 格式:
+ * @access public
+ * @param array $tag 标签属性
+ * @return string|void
+ */
+ public function _radio($tag)
+ {
+ $name = $tag['name'];
+ $radios = $tag['radios'];
+ $checked = $tag['checked'];
+ $separator = $tag['separator'];
+ $radios = $this->tpl->get($radios);
+ $checked = $this->tpl->get($checked) ? $this->tpl->get($checked) : $checked;
+ $parseStr = '';
+ foreach ($radios as $key => $val) {
+ if ($checked == $key) {
+ $parseStr .= ' ' . $val . $separator;
+ } else {
+ $parseStr .= ' ' . $val . $separator;
+ }
+
+ }
+ return $parseStr;
+ }
+
+ /**
+ * list标签解析
+ * 格式:
+ * @access public
+ * @param array $tag 标签属性
+ * @return string
+ */
+ public function _grid($tag)
+ {
+ $id = $tag['id']; //表格ID
+ $datasource = $tag['datasource']; //列表显示的数据源VoList名称
+ $pk = empty($tag['pk']) ? 'id' : $tag['pk']; //主键名,默认为id
+ $style = $tag['style']; //样式名
+ $name = !empty($tag['name']) ? $tag['name'] : 'vo'; //Vo对象名
+ $action = !empty($tag['action']) ? $tag['action'] : false; //是否显示功能操作
+ $key = !empty($tag['key']) ? true : false;
+ if (isset($tag['actionlist'])) {
+ $actionlist = explode(',', trim($tag['actionlist'])); //指定功能列表
+ }
+
+ if (substr($tag['show'], 0, 1) == '$') {
+ $show = $this->tpl->get(substr($tag['show'], 1));
+ } else {
+ $show = $tag['show'];
+ }
+ $show = explode(',', $show); //列表显示字段列表
+
+ //计算表格的列数
+ $colNum = count($show);
+ if (!empty($action)) {
+ $colNum++;
+ }
+
+ if (!empty($key)) {
+ $colNum++;
+ }
+
+ //显示开始
+ $parseStr = "\n";
+ $parseStr .= '';
+ $parseStr .= ' ';
+ $parseStr .= '';
+ //列表需要显示的字段
+ $fields = array();
+ foreach ($show as $val) {
+ $fields[] = explode(':', $val);
+ }
+
+ if (!empty($key)) {
+ $parseStr .= 'No ';
+ }
+ foreach ($fields as $field) {
+//显示指定的字段
+ $property = explode('|', $field[0]);
+ $showname = explode('|', $field[1]);
+ if (isset($showname[1])) {
+ $parseStr .= '';
+ } else {
+ $parseStr .= ' ';
+ }
+ $parseStr .= $showname[0] . ' ';
+ }
+ if (!empty($action)) {
+//如果指定显示操作功能列
+ $parseStr .= '操作 ';
+ }
+ $parseStr .= ' ';
+ $parseStr .= ''; //支持鼠标移动单元行颜色变化 具体方法在js中定义
+
+ if (!empty($key)) {
+ $parseStr .= '{$i} ';
+ }
+ foreach ($fields as $field) {
+ //显示定义的列表字段
+ $parseStr .= '';
+ if (!empty($field[2])) {
+ // 支持列表字段链接功能 具体方法由JS函数实现
+ $href = explode('|', $field[2]);
+ if (count($href) > 1) {
+ //指定链接传的字段值
+ // 支持多个字段传递
+ $array = explode('^', $href[1]);
+ if (count($array) > 1) {
+ foreach ($array as $a) {
+ $temp[] = '\'{$' . $name . '.' . $a . '|addslashes}\'';
+ }
+ $parseStr .= '';
+ } else {
+ $parseStr .= ' ';
+ }
+ } else {
+ //如果没有指定默认传编号值
+ $parseStr .= ' ';
+ }
+ }
+ if (strpos($field[0], '^')) {
+ $property = explode('^', $field[0]);
+ foreach ($property as $p) {
+ $unit = explode('|', $p);
+ if (count($unit) > 1) {
+ $parseStr .= '{$' . $name . '.' . $unit[0] . '|' . $unit[1] . '} ';
+ } else {
+ $parseStr .= '{$' . $name . '.' . $p . '} ';
+ }
+ }
+ } else {
+ $property = explode('|', $field[0]);
+ if (count($property) > 1) {
+ $parseStr .= '{$' . $name . '.' . $property[0] . '|' . $property[1] . '}';
+ } else {
+ $parseStr .= '{$' . $name . '.' . $field[0] . '}';
+ }
+ }
+ if (!empty($field[2])) {
+ $parseStr .= ' ';
+ }
+ $parseStr .= ' ';
+
+ }
+ if (!empty($action)) {
+//显示功能操作
+ if (!empty($actionlist[0])) { //显示指定的功能项
+ $parseStr .= '';
+ foreach ($actionlist as $val) {
+ if (strpos($val, ':')) {
+ $a = explode(':', $val);
+ if (count($a) > 2) {
+ $parseStr .= '' . $a[1] . ' ';
+ } else {
+ $parseStr .= '' . $a[1] . ' ';
+ }
+ } else {
+ $array = explode('|', $val);
+ if (count($array) > 2) {
+ $parseStr .= ' ' . $array[2] . ' ';
+ } else {
+ $parseStr .= ' {$' . $name . '.' . $val . '} ';
+ }
+ }
+ }
+ $parseStr .= ' ';
+ }
+ }
+ $parseStr .= '
';
+ $parseStr .= "\n\n";
+ return $parseStr;
+ }
+
+ /**
+ * list标签解析
+ * 格式:
+ * @access public
+ * @param array $tag 标签属性
+ * @return string
+ */
+ public function _list($tag)
+ {
+ $id = $tag['id']; //表格ID
+ $datasource = $tag['datasource']; //列表显示的数据源VoList名称
+ $pk = empty($tag['pk']) ? 'id' : $tag['pk']; //主键名,默认为id
+ $style = $tag['style']; //样式名
+ $name = !empty($tag['name']) ? $tag['name'] : 'vo'; //Vo对象名
+ $action = 'true' == $tag['action'] ? true : false; //是否显示功能操作
+ $key = !empty($tag['key']) ? true : false;
+ $sort = 'false' == $tag['sort'] ? false : true;
+ $checkbox = $tag['checkbox']; //是否显示Checkbox
+ if (isset($tag['actionlist'])) {
+ if (substr($tag['actionlist'], 0, 1) == '$') {
+ $actionlist = $this->tpl->get(substr($tag['actionlist'], 1));
+ } else {
+ $actionlist = $tag['actionlist'];
+ }
+ $actionlist = explode(',', trim($actionlist)); //指定功能列表
+ }
+
+ if (substr($tag['show'], 0, 1) == '$') {
+ $show = $this->tpl->get(substr($tag['show'], 1));
+ } else {
+ $show = $tag['show'];
+ }
+ $show = explode(',', $show); //列表显示字段列表
+
+ //计算表格的列数
+ $colNum = count($show);
+ if (!empty($checkbox)) {
+ $colNum++;
+ }
+
+ if (!empty($action)) {
+ $colNum++;
+ }
+
+ if (!empty($key)) {
+ $colNum++;
+ }
+
+ //显示开始
+ $parseStr = "\n";
+ $parseStr .= '';
+ $parseStr .= ' ';
+ $parseStr .= '';
+ //列表需要显示的字段
+ $fields = array();
+ foreach ($show as $val) {
+ $fields[] = explode(':', $val);
+ }
+ if (!empty($checkbox) && 'true' == strtolower($checkbox)) {
+//如果指定需要显示checkbox列
+ $parseStr .= ' ';
+ }
+ if (!empty($key)) {
+ $parseStr .= 'No ';
+ }
+ foreach ($fields as $field) {
+//显示指定的字段
+ $property = explode('|', $field[0]);
+ $showname = explode('|', $field[1]);
+ if (isset($showname[1])) {
+ $parseStr .= '';
+ } else {
+ $parseStr .= ' ';
+ }
+ $showname[2] = isset($showname[2]) ? $showname[2] : $showname[0];
+ if ($sort) {
+ $parseStr .= '' . $showname[0] . ' ';
+ } else {
+ $parseStr .= $showname[0] . '';
+ }
+
+ }
+ if (!empty($action)) {
+//如果指定显示操作功能列
+ $parseStr .= '操作 ';
+ }
+
+ $parseStr .= ' ';
+ $parseStr .= '';
+ }
+ if (!empty($key)) {
+ $parseStr .= '{$i} ';
+ }
+ foreach ($fields as $field) {
+ //显示定义的列表字段
+ $parseStr .= '';
+ if (!empty($field[2])) {
+ // 支持列表字段链接功能 具体方法由JS函数实现
+ $href = explode('|', $field[2]);
+ if (count($href) > 1) {
+ //指定链接传的字段值
+ // 支持多个字段传递
+ $array = explode('^', $href[1]);
+ if (count($array) > 1) {
+ foreach ($array as $a) {
+ $temp[] = '\'{$' . $name . '.' . $a . '|addslashes}\'';
+ }
+ $parseStr .= '';
+ } else {
+ $parseStr .= ' ';
+ }
+ } else {
+ //如果没有指定默认传编号值
+ $parseStr .= ' ';
+ }
+ }
+ if (strpos($field[0], '^')) {
+ $property = explode('^', $field[0]);
+ foreach ($property as $p) {
+ $unit = explode('|', $p);
+ if (count($unit) > 1) {
+ $parseStr .= '{$' . $name . '.' . $unit[0] . '|' . $unit[1] . '} ';
+ } else {
+ $parseStr .= '{$' . $name . '.' . $p . '} ';
+ }
+ }
+ } else {
+ $property = explode('|', $field[0]);
+ if (count($property) > 1) {
+ $parseStr .= '{$' . $name . '.' . $property[0] . '|' . $property[1] . '}';
+ } else {
+ $parseStr .= '{$' . $name . '.' . $field[0] . '}';
+ }
+ }
+ if (!empty($field[2])) {
+ $parseStr .= ' ';
+ }
+ $parseStr .= ' ';
+
+ }
+ if (!empty($action)) {
+//显示功能操作
+ if (!empty($actionlist[0])) { //显示指定的功能项
+ $parseStr .= '';
+ foreach ($actionlist as $val) {
+ if (strpos($val, ':')) {
+ $a = explode(':', $val);
+ if (count($a) > 2) {
+ $parseStr .= '' . $a[1] . ' ';
+ } else {
+ $parseStr .= '' . $a[1] . ' ';
+ }
+ } else {
+ $array = explode('|', $val);
+ if (count($array) > 2) {
+ $parseStr .= ' ' . $array[2] . ' ';
+ } else {
+ $parseStr .= ' {$' . $name . '.' . $val . '} ';
+ }
+ }
+ }
+ $parseStr .= ' ';
+ }
+ }
+ $parseStr .= '
';
+ $parseStr .= "\n\n";
+ return $parseStr;
+ }
+}
diff --git a/Framework/Library/Think/Think.class.php b/Framework/Library/Think/Think.class.php
new file mode 100644
index 00000000..c63a9d2f
--- /dev/null
+++ b/Framework/Library/Think/Think.class.php
@@ -0,0 +1,379 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think;
+
+/**
+ * ThinkPHP 引导类
+ */
+class Think
+{
+
+ // 类映射
+ private static $_map = array();
+
+ // 实例化对象
+ private static $_instance = array();
+
+ /**
+ * 应用程序初始化
+ * @access public
+ * @return void
+ */
+ public static function start()
+ {
+ // 注册AUTOLOAD方法
+ spl_autoload_register('Think\Think::autoload');
+ // 设定错误和异常处理
+ register_shutdown_function('Think\Think::fatalError');
+ set_error_handler('Think\Think::appError');
+ set_exception_handler('Think\Think::appException');
+
+ // 初始化文件存储方式
+ Storage::connect(STORAGE_TYPE);
+
+ $runtimefile = RUNTIME_PATH . APP_MODE . '~runtime.php';
+ if (!APP_DEBUG && Storage::has($runtimefile)) {
+ Storage::load($runtimefile);
+ } else {
+ //在高并发状态下,这里容易引起死锁,建议去掉这里的删除和重建,毕竟后面已经有一个删除和重建操作了
+ /*if (Storage::has($runtimefile)) {
+ Storage::unlink($runtimefile);
+ }*/
+
+ $content = '';
+ // 读取应用模式
+ $mode = include is_file(CONF_PATH . 'core.php') ? CONF_PATH . 'core.php' : MODE_PATH . APP_MODE . '.php';
+ // 加载核心文件
+ foreach ($mode['core'] as $file) {
+ if (is_file($file)) {
+ include $file;
+ if (!APP_DEBUG) {
+ $content .= compile($file);
+ }
+
+ }
+ }
+
+ // 加载应用模式配置文件
+ foreach ($mode['config'] as $key => $file) {
+ is_numeric($key) ? C(load_config($file)) : C($key, load_config($file));
+ }
+
+ // 读取当前应用模式对应的配置文件
+ if ('common' != APP_MODE && is_file(CONF_PATH . 'config_' . APP_MODE . CONF_EXT)) {
+ C(load_config(CONF_PATH . 'config_' . APP_MODE . CONF_EXT));
+ }
+
+ // 加载模式别名定义
+ if (isset($mode['alias'])) {
+ self::addMap(is_array($mode['alias']) ? $mode['alias'] : include $mode['alias']);
+ }
+
+ // 加载应用别名定义文件
+ if (is_file(CONF_PATH . 'alias.php')) {
+ self::addMap(include CONF_PATH . 'alias.php');
+ }
+
+ // 加载模式行为定义
+ if (isset($mode['tags'])) {
+ Hook::import(is_array($mode['tags']) ? $mode['tags'] : include $mode['tags']);
+ }
+
+ // 加载应用行为定义
+ if (is_file(CONF_PATH . 'tags.php'))
+ // 允许应用增加开发模式配置定义
+ {
+ Hook::import(include CONF_PATH . 'tags.php');
+ }
+
+ // 加载框架底层语言包
+ L(include THINK_PATH . 'Lang/' . strtolower(C('DEFAULT_LANG')) . '.php');
+
+ if (!APP_DEBUG) {
+ $content .= "\nnamespace { Think\\Think::addMap(" . var_export(self::$_map, true) . ");";
+ $content .= "\nL(" . var_export(L(), true) . ");\nC(" . var_export(C(), true) . ');Think\Hook::import(' . var_export(Hook::get(), true) . ');}';
+ Storage::put($runtimefile, strip_whitespace('getMessage();
+ $trace = $e->getTrace();
+ if ('E' == $trace[0]['function']) {
+ $error['file'] = $trace[0]['file'];
+ $error['line'] = $trace[0]['line'];
+ } else {
+ $error['file'] = $e->getFile();
+ $error['line'] = $e->getLine();
+ }
+ $error['trace'] = $e->getTraceAsString();
+ Log::record($error['message'], Log::ERR);
+ // 发送404信息
+ header('HTTP/1.1 404 Not Found');
+ header('Status:404 Not Found');
+ self::halt($error);
+ }
+
+ /**
+ * 自定义错误处理
+ * @access public
+ * @param int $errno 错误类型
+ * @param string $errstr 错误信息
+ * @param string $errfile 错误文件
+ * @param int $errline 错误行数
+ * @return void
+ */
+ public static function appError($errno, $errstr, $errfile, $errline)
+ {
+ switch ($errno) {
+ case E_ERROR:
+ case E_PARSE:
+ case E_CORE_ERROR:
+ case E_COMPILE_ERROR:
+ case E_USER_ERROR:
+ ob_end_clean();
+ $errorStr = "$errstr " . $errfile . " 第 $errline 行.";
+ if (C('LOG_RECORD')) {
+ Log::write("[$errno] " . $errorStr, Log::ERR);
+ }
+
+ self::halt($errorStr);
+ break;
+ default:
+ $errorStr = "[$errno] $errstr " . $errfile . " 第 $errline 行.";
+ self::trace($errorStr, '', 'NOTIC');
+ break;
+ }
+ }
+
+ // 致命错误捕获
+ public static function fatalError()
+ {
+ Log::save();
+ if ($e = error_get_last()) {
+ switch ($e['type']) {
+ case E_ERROR:
+ case E_PARSE:
+ case E_CORE_ERROR:
+ case E_COMPILE_ERROR:
+ case E_USER_ERROR:
+ ob_end_clean();
+ self::halt($e);
+ break;
+ }
+ }
+ }
+
+ /**
+ * 错误输出
+ * @param mixed $error 错误
+ * @return void
+ */
+ public static function halt($error)
+ {
+ $e = array();
+ if (APP_DEBUG || IS_CLI) {
+ //调试模式下输出错误信息
+ if (!is_array($error)) {
+ $trace = debug_backtrace();
+ $e['message'] = $error;
+ $e['file'] = $trace[0]['file'];
+ $e['line'] = $trace[0]['line'];
+ ob_start();
+ debug_print_backtrace();
+ $e['trace'] = ob_get_clean();
+ } else {
+ $e = $error;
+ }
+ if (IS_CLI) {
+ exit((IS_WIN ? iconv('UTF-8', 'gbk', $e['message']) : $e['message']) . PHP_EOL . 'FILE: ' . $e['file'] . '(' . $e['line'] . ')' . PHP_EOL . $e['trace']);
+ }
+ } else {
+ //否则定向到错误页面
+ $error_page = C('ERROR_PAGE');
+ if (!empty($error_page)) {
+ redirect($error_page);
+ } else {
+ $message = is_array($error) ? $error['message'] : $error;
+ $e['message'] = C('SHOW_ERROR_MSG') ? $message : C('ERROR_MESSAGE');
+ }
+ }
+ // 包含异常页面模板
+ $exceptionFile = C('TMPL_EXCEPTION_FILE', null, THINK_PATH . 'Tpl/think_exception.tpl');
+ include $exceptionFile;
+ exit;
+ }
+
+ /**
+ * 添加和获取页面Trace记录
+ * @param string $value 变量
+ * @param string $label 标签
+ * @param string $level 日志级别(或者页面Trace的选项卡)
+ * @param boolean $record 是否记录日志
+ * @return void|array
+ */
+ public static function trace($value = '[think]', $label = '', $level = 'DEBUG', $record = false)
+ {
+ static $_trace = array();
+ if ('[think]' === $value) {
+ // 获取trace信息
+ return $_trace;
+ } else {
+ $info = ($label ? $label . ':' : '') . print_r($value, true);
+ $level = strtoupper($level);
+
+ if ((defined('IS_AJAX') && IS_AJAX) || !C('SHOW_PAGE_TRACE') || $record) {
+ Log::record($info, $level, $record);
+ } else {
+ if (!isset($_trace[$level]) || count($_trace[$level]) > C('TRACE_MAX_RECORD')) {
+ $_trace[$level] = array();
+ }
+ $_trace[$level][] = $info;
+ }
+ }
+ }
+}
diff --git a/Framework/Library/Think/Upload.class.php b/Framework/Library/Think/Upload.class.php
new file mode 100644
index 00000000..3dd4cf5b
--- /dev/null
+++ b/Framework/Library/Think/Upload.class.php
@@ -0,0 +1,453 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+class Upload
+{
+ /**
+ * 默认上传配置
+ * @var array
+ */
+ private $config = array(
+ 'mimes' => array(), //允许上传的文件MiMe类型
+ 'maxSize' => 0, //上传的文件大小限制 (0-不做限制)
+ 'exts' => array(), //允许上传的文件后缀
+ 'autoSub' => true, //自动子目录保存文件
+ 'subName' => array('date', 'Y-m-d'), //子目录创建方式,[0]-函数名,[1]-参数,多个参数使用数组
+ 'rootPath' => './Uploads/', //保存根路径
+ 'savePath' => '', //保存路径
+ 'saveName' => array('uniqid', ''), //上传文件命名规则,[0]-函数名,[1]-参数,多个参数使用数组
+ 'saveExt' => '', //文件保存后缀,空则使用原后缀
+ 'replace' => false, //存在同名是否覆盖
+ 'hash' => true, //是否生成hash编码
+ 'callback' => false, //检测文件是否存在回调,如果存在返回文件信息数组
+ 'driver' => '', // 文件上传驱动
+ 'driverConfig' => array(), // 上传驱动配置
+ );
+
+ /**
+ * 上传错误信息
+ * @var string
+ */
+ private $error = ''; //上传错误信息
+
+ /**
+ * 上传驱动实例
+ * @var Object
+ */
+ private $uploader;
+
+ /**
+ * 构造方法,用于构造上传实例
+ * @param array $config 配置
+ * @param string $driver 要使用的上传驱动 LOCAL-本地上传驱动,FTP-FTP上传驱动
+ */
+ public function __construct($config = array(), $driver = '', $driverConfig = null)
+ {
+ /* 获取配置 */
+ $this->config = array_merge($this->config, $config);
+
+ /* 设置上传驱动 */
+ $this->setDriver($driver, $driverConfig);
+
+ /* 调整配置,把字符串配置参数转换为数组 */
+ if (!empty($this->config['mimes'])) {
+ if (is_string($this->mimes)) {
+ $this->config['mimes'] = explode(',', $this->mimes);
+ }
+ $this->config['mimes'] = array_map('strtolower', $this->mimes);
+ }
+ if (!empty($this->config['exts'])) {
+ if (is_string($this->exts)) {
+ $this->config['exts'] = explode(',', $this->exts);
+ }
+ $this->config['exts'] = array_map('strtolower', $this->exts);
+ }
+ }
+
+ /**
+ * 使用 $this->name 获取配置
+ * @param string $name 配置名称
+ * @return multitype 配置值
+ */
+ public function __get($name)
+ {
+ return $this->config[$name];
+ }
+
+ public function __set($name, $value)
+ {
+ if (isset($this->config[$name])) {
+ $this->config[$name] = $value;
+ if ('driverConfig' == $name) {
+ //改变驱动配置后重置上传驱动
+ //注意:必须选改变驱动然后再改变驱动配置
+ $this->setDriver();
+ }
+ }
+ }
+
+ public function __isset($name)
+ {
+ return isset($this->config[$name]);
+ }
+
+ /**
+ * 获取最后一次上传错误信息
+ * @return string 错误信息
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * 上传单个文件
+ * @param array $file 文件数组
+ * @return array 上传成功后的文件信息
+ */
+ public function uploadOne($file)
+ {
+ $info = $this->upload(array($file));
+ return $info ? $info[0] : $info;
+ }
+
+ /**
+ * 上传文件
+ * @param 文件信息数组 $files ,通常是 $_FILES数组
+ */
+ public function upload($files = '')
+ {
+ if ('' === $files) {
+ $files = $_FILES;
+ }
+ if (empty($files)) {
+ $this->error = '没有上传的文件!';
+ return false;
+ }
+
+ /* 检测上传根目录 */
+ if (!$this->uploader->checkRootPath($this->rootPath)) {
+ $this->error = $this->uploader->getError();
+ return false;
+ }
+
+ /* 检查上传目录 */
+ if (!$this->uploader->checkSavePath($this->savePath)) {
+ $this->error = $this->uploader->getError();
+ return false;
+ }
+
+ /* 逐个检测并上传文件 */
+ $info = array();
+ if (function_exists('finfo_open')) {
+ $finfo = finfo_open(FILEINFO_MIME_TYPE);
+ }
+ // 对上传文件数组信息处理
+ $files = $this->dealFiles($files);
+ foreach ($files as $key => $file) {
+ $file['name'] = strip_tags($file['name']);
+ if (!isset($file['key'])) {
+ $file['key'] = $key;
+ }
+
+ /* 通过扩展获取文件类型,可解决FLASH上传$FILES数组返回文件类型错误的问题 */
+ if (isset($finfo)) {
+ $file['type'] = finfo_file($finfo, $file['tmp_name']);
+ }
+
+ /* 获取上传文件后缀,允许上传无后缀文件 */
+ $file['ext'] = pathinfo($file['name'], PATHINFO_EXTENSION);
+
+ /* 文件上传检测 */
+ if (!$this->check($file)) {
+ continue;
+ }
+
+ /* 获取文件hash */
+ if ($this->hash) {
+ $file['md5'] = md5_file($file['tmp_name']);
+ $file['sha1'] = sha1_file($file['tmp_name']);
+ }
+
+ /* 调用回调函数检测文件是否存在 */
+ $data = call_user_func($this->callback, $file);
+ if ($this->callback && $data) {
+ if (file_exists('.' . $data['path'])) {
+ $info[$key] = $data;
+ continue;
+ } elseif ($this->removeTrash) {
+ call_user_func($this->removeTrash, $data); //删除垃圾据
+ }
+ }
+
+ /* 生成保存文件名 */
+ $savename = $this->getSaveName($file);
+ if (false == $savename) {
+ continue;
+ } else {
+ $file['savename'] = $savename;
+ }
+
+ /* 检测并创建子目录 */
+ $subpath = $this->getSubPath($file['name']);
+ if (false === $subpath) {
+ continue;
+ } else {
+ $file['savepath'] = $this->savePath . $subpath;
+ }
+
+ /* 对图像文件进行严格检测 */
+ $ext = strtolower($file['ext']);
+ if (in_array($ext, array('gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf'))) {
+ $imginfo = getimagesize($file['tmp_name']);
+ if (empty($imginfo) || ('gif' == $ext && empty($imginfo['bits']))) {
+ $this->error = '非法图像文件!';
+ continue;
+ }
+ }
+
+ /* 保存文件 并记录保存成功的文件 */
+ if ($this->uploader->save($file, $this->replace)) {
+ unset($file['error'], $file['tmp_name']);
+ $info[$key] = $file;
+ } else {
+ $this->error = $this->uploader->getError();
+ }
+ }
+ if (isset($finfo)) {
+ finfo_close($finfo);
+ }
+ return empty($info) ? false : $info;
+ }
+
+ /**
+ * 转换上传文件数组变量为正确的方式
+ * @access private
+ * @param array $files 上传的文件变量
+ * @return array
+ */
+ private function dealFiles($files)
+ {
+ $fileArray = array();
+ $n = 0;
+ foreach ($files as $key => $file) {
+ if (is_array($file['name'])) {
+ $keys = array_keys($file);
+ $count = count($file['name']);
+ for ($i = 0; $i < $count; $i++) {
+ $fileArray[$n]['key'] = $key;
+ foreach ($keys as $_key) {
+ $fileArray[$n][$_key] = $file[$_key][$i];
+ }
+ $n++;
+ }
+ } else {
+ $fileArray = $files;
+ break;
+ }
+ }
+ return $fileArray;
+ }
+
+ /**
+ * 设置上传驱动
+ * @param string $driver 驱动名称
+ * @param array $config 驱动配置
+ */
+ private function setDriver($driver = null, $config = null)
+ {
+ $driver = $driver ?: ($this->driver ?: C('FILE_UPLOAD_TYPE'));
+ $config = $config ?: ($this->driverConfig ?: C('UPLOAD_TYPE_CONFIG'));
+ $class = strpos($driver, '\\') ? $driver : 'Think\\Upload\\Driver\\' . ucfirst(strtolower($driver));
+ $this->uploader = new $class($config);
+ if (!$this->uploader) {
+ E("不存在上传驱动:{$name}");
+ }
+ }
+
+ /**
+ * 检查上传的文件
+ * @param array $file 文件信息
+ */
+ private function check($file)
+ {
+ /* 文件上传失败,捕获错误代码 */
+ if ($file['error']) {
+ $this->error($file['error']);
+ return false;
+ }
+
+ /* 无效上传 */
+ if (empty($file['name'])) {
+ $this->error = '未知上传错误!';
+ }
+
+ /* 检查是否合法上传 */
+ if (!is_uploaded_file($file['tmp_name'])) {
+ $this->error = '非法上传文件!';
+ return false;
+ }
+
+ /* 检查文件大小 */
+ if (!$this->checkSize($file['size'])) {
+ $this->error = '上传文件大小不符!';
+ return false;
+ }
+
+ /* 检查文件Mime类型 */
+ //TODO:FLASH上传的文件获取到的mime类型都为application/octet-stream
+ if (!$this->checkMime($file['type'])) {
+ $this->error = '上传文件MIME类型不允许!';
+ return false;
+ }
+
+ /* 检查文件后缀 */
+ if (!$this->checkExt($file['ext'])) {
+ $this->error = '上传文件后缀不允许';
+ return false;
+ }
+
+ /* 通过检测 */
+ return true;
+ }
+
+ /**
+ * 获取错误代码信息
+ * @param string $errorNo 错误号
+ */
+ private function error($errorNo)
+ {
+ switch ($errorNo) {
+ case 1:
+ $this->error = '上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值!';
+ break;
+ case 2:
+ $this->error = '上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值!';
+ break;
+ case 3:
+ $this->error = '文件只有部分被上传!';
+ break;
+ case 4:
+ $this->error = '没有文件被上传!';
+ break;
+ case 6:
+ $this->error = '找不到临时文件夹!';
+ break;
+ case 7:
+ $this->error = '文件写入失败!';
+ break;
+ default:
+ $this->error = '未知上传错误!';
+ }
+ }
+
+ /**
+ * 检查文件大小是否合法
+ * @param integer $size 数据
+ */
+ private function checkSize($size)
+ {
+ return !($size > $this->maxSize) || (0 == $this->maxSize);
+ }
+
+ /**
+ * 检查上传的文件MIME类型是否合法
+ * @param string $mime 数据
+ */
+ private function checkMime($mime)
+ {
+ return empty($this->config['mimes']) ? true : in_array(strtolower($mime), $this->mimes);
+ }
+
+ /**
+ * 检查上传的文件后缀是否合法
+ * @param string $ext 后缀
+ */
+ private function checkExt($ext)
+ {
+ return empty($this->config['exts']) ? true : in_array(strtolower($ext), $this->exts);
+ }
+
+ /**
+ * 根据上传文件命名规则取得保存文件名
+ * @param string $file 文件信息
+ */
+ private function getSaveName($file)
+ {
+ $rule = $this->saveName;
+ if (empty($rule)) {
+ //保持文件名不变
+ /* 解决pathinfo中文文件名BUG */
+ $filename = substr(pathinfo("_{$file['name']}", PATHINFO_FILENAME), 1);
+ $savename = $filename;
+ } else {
+ $savename = $this->getName($rule, $file['name']);
+ if (empty($savename)) {
+ $this->error = '文件命名规则错误!';
+ return false;
+ }
+ }
+
+ /* 文件保存后缀,支持强制更改文件后缀 */
+ $ext = empty($this->config['saveExt']) ? $file['ext'] : $this->saveExt;
+
+ return $savename . '.' . $ext;
+ }
+
+ /**
+ * 获取子目录的名称
+ * @param array $file 上传的文件信息
+ */
+ private function getSubPath($filename)
+ {
+ $subpath = '';
+ $rule = $this->subName;
+ if ($this->autoSub && !empty($rule)) {
+ $subpath = $this->getName($rule, $filename) . '/';
+
+ if (!empty($subpath) && !$this->uploader->mkdir($this->savePath . $subpath)) {
+ $this->error = $this->uploader->getError();
+ return false;
+ }
+ }
+ return $subpath;
+ }
+
+ /**
+ * 根据指定的规则获取文件或目录名称
+ * @param array $rule 规则
+ * @param string $filename 原文件名
+ * @return string 文件或目录名称
+ */
+ private function getName($rule, $filename)
+ {
+ $name = '';
+ if (is_array($rule)) {
+ //数组规则
+ $func = $rule[0];
+ $param = (array) $rule[1];
+ foreach ($param as &$value) {
+ $value = str_replace('__FILE__', $filename, $value);
+ }
+ $name = call_user_func_array($func, $param);
+ } elseif (is_string($rule)) {
+ //字符串规则
+ if (function_exists($rule)) {
+ $name = call_user_func($rule);
+ } else {
+ $name = $rule;
+ }
+ }
+ return $name;
+ }
+
+}
diff --git a/Framework/Library/Think/Upload/Driver/Bcs.class.php b/Framework/Library/Think/Upload/Driver/Bcs.class.php
new file mode 100644
index 00000000..6a968f59
--- /dev/null
+++ b/Framework/Library/Think/Upload/Driver/Bcs.class.php
@@ -0,0 +1,257 @@
+
+// +----------------------------------------------------------------------
+namespace Think\Upload\Driver;
+
+use Think\Upload\Driver\Bcs\BaiduBcs;
+
+class Bcs
+{
+ /**
+ * 上传文件根目录
+ * @var string
+ */
+ private $rootPath;
+ const DEFAULT_URL = 'bcs.duapp.com';
+
+ /**
+ * 上传错误信息
+ * @var string
+ */
+ private $error = '';
+
+ public $config = array(
+ 'AccessKey' => '',
+ 'SecretKey' => '', //百度云服务器
+ 'bucket' => '', //空间名称
+ 'rename' => false,
+ 'timeout' => 3600, //超时时间
+ );
+
+ public $bcs = null;
+
+ /**
+ * 构造函数,用于设置上传根路径
+ * @param array $config FTP配置
+ */
+ public function __construct($config)
+ {
+ /* 默认FTP配置 */
+ $this->config = array_merge($this->config, $config);
+
+ $bcsClass = dirname(__FILE__) . "/Bcs/bcs.class.php";
+ if (is_file($bcsClass)) {
+ require_once $bcsClass;
+ }
+
+ $this->bcs = new BaiduBCS($this->config['AccessKey'], $this->config['SecretKey'], self::DEFAULT_URL);
+ }
+
+ /**
+ * 检测上传根目录(百度云上传时支持自动创建目录,直接返回)
+ * @param string $rootpath 根目录
+ * @return boolean true-检测通过,false-检测失败
+ */
+ public function checkRootPath($rootpath)
+ {
+ /* 设置根目录 */
+ $this->rootPath = str_replace('./', '/', $rootpath);
+ return true;
+ }
+
+ /**
+ * 检测上传目录(百度云上传时支持自动创建目录,直接返回)
+ * @param string $savepath 上传目录
+ * @return boolean 检测结果,true-通过,false-失败
+ */
+ public function checkSavePath($savepath)
+ {
+ return true;
+ }
+
+ /**
+ * 创建文件夹 (百度云上传时支持自动创建目录,直接返回)
+ * @param string $savepath 目录名称
+ * @return boolean true-创建成功,false-创建失败
+ */
+ public function mkdir($savepath)
+ {
+ return true;
+ }
+
+ /**
+ * 保存指定文件
+ * @param array $file 保存的文件信息
+ * @param boolean $replace 同名文件是否覆盖
+ * @return boolean 保存状态,true-成功,false-失败
+ */
+ public function save(&$file, $replace = true)
+ {
+ $opt = array();
+ $opt['acl'] = BaiduBCS::BCS_SDK_ACL_TYPE_PUBLIC_WRITE;
+ $opt['curlopts'] = array(
+ CURLOPT_CONNECTTIMEOUT => 10,
+ CURLOPT_TIMEOUT => 1800,
+ );
+ $object = "/{$file['savepath']}{$file['savename']}";
+ $response = $this->bcs->create_object($this->config['bucket'], $object, $file['tmp_name'], $opt);
+ $url = $this->download($object);
+ $file['url'] = $url;
+ return $response->isOK() ? true : false;
+ }
+
+ public function download($file)
+ {
+ $file = str_replace('./', '/', $file);
+ $opt = array();
+ $opt['time'] = mktime('2049-12-31'); //这是最长有效时间!--
+ $response = $this->bcs->generate_get_object_url($this->config['bucket'], $file, $opt);
+ return $response;
+ }
+
+ /**
+ * 获取最后一次上传错误信息
+ * @return string 错误信息
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * 请求百度云服务器
+ * @param string $path 请求的PATH
+ * @param string $method 请求方法
+ * @param array $headers 请求header
+ * @param resource $body 上传文件资源
+ * @return boolean
+ */
+ private function request($path, $method, $headers = null, $body = null)
+ {
+ $ch = curl_init($path);
+
+ $_headers = array('Expect:');
+ if (!is_null($headers) && is_array($headers)) {
+ foreach ($headers as $k => $v) {
+ array_push($_headers, "{$k}: {$v}");
+ }
+ }
+
+ $length = 0;
+ $date = gmdate('D, d M Y H:i:s \G\M\T');
+
+ if (!is_null($body)) {
+ if (is_resource($body)) {
+ fseek($body, 0, SEEK_END);
+ $length = ftell($body);
+ fseek($body, 0);
+
+ array_push($_headers, "Content-Length: {$length}");
+ curl_setopt($ch, CURLOPT_INFILE, $body);
+ curl_setopt($ch, CURLOPT_INFILESIZE, $length);
+ } else {
+ $length = @strlen($body);
+ array_push($_headers, "Content-Length: {$length}");
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
+ }
+ } else {
+ array_push($_headers, "Content-Length: {$length}");
+ }
+
+ // array_push($_headers, 'Authorization: ' . $this->sign($method, $uri, $date, $length));
+ array_push($_headers, "Date: {$date}");
+
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $_headers);
+ curl_setopt($ch, CURLOPT_TIMEOUT, $this->config['timeout']);
+ curl_setopt($ch, CURLOPT_HEADER, 1);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
+ curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
+
+ if ('PUT' == $method || 'POST' == $method) {
+ curl_setopt($ch, CURLOPT_POST, 1);
+ } else {
+ curl_setopt($ch, CURLOPT_POST, 0);
+ }
+
+ if ('HEAD' == $method) {
+ curl_setopt($ch, CURLOPT_NOBODY, true);
+ }
+
+ $response = curl_exec($ch);
+ $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ curl_close($ch);
+ list($header, $body) = explode("\r\n\r\n", $response, 2);
+
+ if (200 == $status) {
+ if ('GET' == $method) {
+ return $body;
+ } else {
+ $data = $this->response($header);
+ return count($data) > 0 ? $data : true;
+ }
+ } else {
+ $this->error($header);
+ return false;
+ }
+ }
+
+ /**
+ * 获取响应数据
+ * @param string $text 响应头字符串
+ * @return array 响应数据列表
+ */
+ private function response($text)
+ {
+ $items = json_decode($text, true);
+ return $items;
+ }
+
+ /**
+ * 生成请求签名
+ * @return string 请求签名
+ */
+ private function sign($method, $Bucket, $object = '/', $size = '')
+ {
+ if (!$size) {
+ $size = $this->config['size'];
+ }
+
+ $param = array(
+ 'ak' => $this->config['AccessKey'],
+ 'sk' => $this->config['SecretKey'],
+ 'size' => $size,
+ 'bucket' => $Bucket,
+ 'host' => self::DEFAULT_URL,
+ 'date' => time() + $this->config['timeout'],
+ 'ip' => '',
+ 'object' => $object,
+ );
+ $response = $this->request($this->apiurl . '?' . http_build_query($param), 'POST');
+ if ($response) {
+ $response = json_decode($response, true);
+ }
+
+ return $response['content'][$method];
+ }
+
+ /**
+ * 获取请求错误信息
+ * @param string $header 请求返回头信息
+ */
+ private function error($header)
+ {
+ list($status, $stash) = explode("\r\n", $header, 2);
+ list($v, $code, $message) = explode(" ", $status, 3);
+ $message = is_null($message) ? 'File Not Found' : "[{$status}]:{$message}";
+ $this->error = $message;
+ }
+
+}
diff --git a/Framework/Library/Think/Upload/Driver/Bcs/bcs.class.php b/Framework/Library/Think/Upload/Driver/Bcs/bcs.class.php
new file mode 100644
index 00000000..d41b0cd8
--- /dev/null
+++ b/Framework/Library/Think/Upload/Driver/Bcs/bcs.class.php
@@ -0,0 +1,1364 @@
+ak = $ak;
+ $this->sk = $sk;
+ } elseif (defined('BCS_AK') && defined('BCS_SK') && strlen(BCS_AK) > 0 && strlen(BCS_SK) > 0) {
+ $this->ak = BCS_AK;
+ $this->sk = BCS_SK;
+ } elseif (false !== getenv('HTTP_BAE_ENV_AK') && false !== getenv('HTTP_BAE_ENV_SK')) {
+ $this->ak = getenv('HTTP_BAE_ENV_AK');
+ $this->sk = getenv('HTTP_BAE_ENV_SK');
+ } else {
+ throw new BCS_Exception('Construct can not get ak &sk pair, please check!');
+ }
+ //valid $hostname
+ if (null !== $hostname) {
+ $this->hostname = $hostname;
+ } elseif (false !== getenv('HTTP_BAE_ENV_ADDR_BCS')) {
+ $this->hostname = getenv('HTTP_BAE_ENV_ADDR_BCS');
+ } else {
+ $this->hostname = self::DEFAULT_URL;
+ }
+ }
+
+ /**
+ * 将消息发往Baidu BCS.
+ * @param array $opt
+ * @return BCS_ResponseCore
+ */
+ private function authenticate($opt)
+ {
+ //set common param into opt
+ $opt[self::AK] = $this->ak;
+ $opt[self::SK] = $this->sk;
+
+ // Validate the S3 bucket name, only list_bucket didnot need validate_bucket
+ if (!('/' == $opt[self::OBJECT] && '' == $opt[self::BUCKET] && 'GET' == $opt[self::METHOD] && !isset($opt[self::QUERY_STRING][self::ACL])) && !self::validateBucket($opt[self::BUCKET])) {
+ throw new BCS_Exception($opt[self::BUCKET] . 'is not valid, please check!');
+ }
+ //Validate object
+ if (isset($opt[self::OBJECT]) && !self::validateObject($opt[self::OBJECT])) {
+ throw new BCS_Exception("Invalid object param[" . $opt[self::OBJECT] . "], please check.", -1);
+ }
+ //construct url
+ $url = $this->formatUrl($opt);
+ if (false === $url) {
+ throw new BCS_Exception('Can not format url, please check your param!', -1);
+ }
+ $opt['url'] = $url;
+ $this->log("[method:" . $opt[self::METHOD] . "][url:$url]", $opt);
+ //build request
+ $request = new BCS_RequestCore($opt['url']);
+ $headers = array(
+ 'Content-Type' => 'application/x-www-form-urlencoded');
+
+ $request->set_method($opt[self::METHOD]);
+ //Write get_object content to fileWriteTo
+ if (isset($opt['fileWriteTo'])) {
+ $request->set_write_file($opt['fileWriteTo']);
+ }
+ // Merge the HTTP headers
+ if (isset($opt[self::HEADERS])) {
+ $headers = array_merge($headers, $opt[self::HEADERS]);
+ }
+ // Set content to Http-Body
+ if (isset($opt['content'])) {
+ $request->set_body($opt['content']);
+ }
+ // Upload file
+ if (isset($opt['fileUpload'])) {
+ if (!file_exists($opt['fileUpload'])) {
+ throw new BCS_Exception('File[' . $opt['fileUpload'] . '] not found!', -1);
+ }
+ $request->set_read_file($opt['fileUpload']);
+ // Determine the length to read from the file
+ $length = $request->read_stream_size; // The file size by default
+ $file_size = $length;
+ if (isset($opt["length"])) {
+ if ($opt["length"] > $file_size) {
+ throw new BCS_Exception("Input opt[length] invalid! It can not bigger than file-size", -1);
+ }
+ $length = $opt['length'];
+ }
+ if (isset($opt['seekTo']) && !isset($opt["length"])) {
+ // Read from seekTo until EOF by default, when set seekTo but not set $opt["length"]
+ $length -= (integer) $opt['seekTo'];
+ }
+ $request->set_read_stream_size($length);
+ // Attempt to guess the correct mime-type
+ if ('application/x-www-form-urlencoded' === $headers['Content-Type']) {
+ $extension = explode('.', $opt['fileUpload']);
+ $extension = array_pop($extension);
+ $mime_type = BCS_MimeTypes::get_mimetype($extension);
+ $headers['Content-Type'] = $mime_type;
+ }
+ $headers['Content-MD5'] = '';
+ }
+ // Handle streaming file offsets
+ if (isset($opt['seekTo'])) {
+ // Pass the seek position to BCS_RequestCore
+ $request->set_seek_position((integer) $opt['seekTo']);
+ }
+ // Add headers to request and compute the string to sign
+ foreach ($headers as $header_key => $header_value) {
+ // Strip linebreaks from header values as they're illegal and can allow for security issues
+ $header_value = str_replace(array(
+ "\r",
+ "\n"), '', $header_value);
+ // Add the header if it has a value
+ if ('' !== $header_value) {
+ $request->add_header($header_key, $header_value);
+ }
+ }
+ // Set the curl options.
+ if (isset($opt['curlopts']) && count($opt['curlopts'])) {
+ $request->set_curlopts($opt['curlopts']);
+ }
+ $request->send_request();
+ require_once dirname(__FILE__) . "/requestcore.class.php";
+ return new BCS_ResponseCore($request->get_response_header(), $request->get_response_body(), $request->get_response_code());
+ }
+
+ /**
+ * 获取当前密钥对拥有者的bucket列表
+ * @param array $opt (Optional)
+ * BaiduBCS::IMPORT_BCS_LOG_METHOD - String - Optional: 支持用户传入日志处理函数,函数定义如 function f($log)
+ * @throws BCS_Exception
+ * @return BCS_ResponseCore
+ */
+ public function listBucket($opt = array())
+ {
+ $this->assertParameterArray($opt);
+ $opt[self::BUCKET] = '';
+ $opt[self::METHOD] = 'GET';
+ $opt[self::OBJECT] = '/';
+ $response = $this->authenticate($opt);
+ $this->log($response->isOK() ? "List bucket success!" : "List bucket failed! Response: [" . $response->body . "]", $opt);
+ return $response;
+ }
+
+ /**
+ * 创建 bucket
+ * @param string $bucket (Required) bucket名称
+ * @param string $acl (Optional) bucket权限设置,若为null,使用server分配的默认权限
+ * @param array $opt (Optional)
+ * @throws BCS_Exception
+ * @return BCS_ResponseCore
+ */
+ public function createBucket($bucket, $acl = null, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ $opt[self::BUCKET] = $bucket;
+ $opt[self::METHOD] = 'PUT';
+ $opt[self::OBJECT] = '/';
+ if (null !== $acl) {
+ if (!in_array($acl, self::$ACL_TYPES)) {
+ throw new BCS_Exception("Invalid acl_type[" . $acl . "], please check!", -1);
+ }
+ self::setHeaderIntoOpt("x-bs-acl", $acl, $opt);
+ }
+ $response = $this->authenticate($opt);
+ $this->log($response->isOK() ? "Create bucket success!" : "Create bucket failed! Response: [" . $response->body . "]", $opt);
+ return $response;
+ }
+
+ /**
+ * 删除bucket
+ * @param string $bucket (Required)
+ * @param array $opt (Optional)
+ * @return boolean|BCS_ResponseCore
+ */
+ public function deleteBucket($bucket, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ $opt[self::BUCKET] = $bucket;
+ $opt[self::METHOD] = 'DELETE';
+ $opt[self::OBJECT] = '/';
+ $response = $this->authenticate($opt);
+ $this->log($response->isOK() ? "Delete bucket success!" : "Delete bucket failed! Response: [" . $response->body . "]", $opt);
+ return $response;
+ }
+
+ /**
+ * 设置bucket的acl,有三种模式,
+ * (1).设置详细json格式的acl;
+ * a. $acl 为json的array
+ * b. $acl 为json的string
+ * (2).通过acl_type字段进行设置
+ * a. $acl 为BaiduBCS::$ACL_TYPES中的字段
+ * @param string $bucket (Required)
+ * @param string $acl (Required)
+ * @param array $opt (Optional)
+ * @return boolean|BCS_ResponseCore
+ */
+ public function setBucketAcl($bucket, $acl, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ $result = $this->analyzeUserAcl($acl);
+ $opt = array_merge($opt, $result);
+ $opt[self::BUCKET] = $bucket;
+ $opt[self::METHOD] = 'PUT';
+ $opt[self::OBJECT] = '/';
+ $opt[self::QUERY_STRING] = array(
+ self::ACL => 1);
+ $response = $this->authenticate($opt);
+ $this->log($response->isOK() ? "Set bucket acl success!" : "Set bucket acl failed! Response: [" . $response->body . "]", $opt);
+ return $response;
+ }
+
+ /**
+ * 获取bucket的acl
+ * @param string $bucket (Required)
+ * @param array $opt (Optional)
+ * @return BCS_ResponseCore
+ */
+ public function getBucketAcl($bucket, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ $opt[self::BUCKET] = $bucket;
+ $opt[self::METHOD] = 'GET';
+ $opt[self::OBJECT] = '/';
+ $opt[self::QUERY_STRING] = array(
+ self::ACL => 1);
+ $response = $this->authenticate($opt);
+ $this->log($response->isOK() ? "Get bucket acl success!" : "Get bucket acl failed! Response: [" . $response->body . "]", $opt);
+ return $response;
+ }
+
+ /**
+ * 获取bucket中object列表
+ * @param string $bucket (Required)
+ * @param array $opt (Optional)
+ * start : 主要用于翻页功能,用法同mysql中start的用法
+ * limit : 主要用于翻页功能,用法同mysql中limit的用法
+ * prefix: 只返回以prefix为前缀的object,此处prefix必须以'/'开头
+ * @throws BCS_Exception
+ * @return BCS_ResponseCore
+ */
+ public function listObject($bucket, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ $opt[self::BUCKET] = $bucket;
+ if (empty($opt[self::BUCKET])) {
+ throw new BCS_Exception("Bucket should not be empty, please check", -1);
+ }
+ $opt[self::METHOD] = 'GET';
+ $opt[self::OBJECT] = '/';
+ $opt[self::QUERY_STRING] = array();
+ if (isset($opt['start']) && is_int($opt['start'])) {
+ $opt[self::QUERY_STRING]['start'] = $opt['start'];
+ }
+ if (isset($opt['limit']) && is_int($opt['limit'])) {
+ $opt[self::QUERY_STRING]['limit'] = $opt['limit'];
+ }
+ if (isset($opt['prefix'])) {
+ $opt[self::QUERY_STRING]['prefix'] = rawurlencode($opt['prefix']);
+ }
+ $response = $this->authenticate($opt);
+ $this->log($response->isOK() ? "List object success!" : "Lit object failed! Response: [" . $response->body . "]", $opt);
+ return $response;
+ }
+
+ /**
+ * 以目录形式获取bucket中object列表
+ * @param string $bucket (Required)
+ * @param $dir (Required)
+ * 目录名,格式为必须以'/'开头和结尾,默认为'/'
+ * @param string $list_model (Required)
+ * 目录展现形式,值可以为0,1,2,默认为2,以下对各个值的功能进行介绍:
+ * 0->只返回object列表,不返回子目录列表
+ * 1->只返回子目录列表,不返回object列表
+ * 2->同时返回子目录列表和object列表
+ * @param array $opt (Optional)
+ * start : 主要用于翻页功能,用法同mysql中start的用法
+ * limit : 主要用于翻页功能,用法同mysql中limit的用法
+ * @throws BCS_Exception
+ * @return BCS_ResponseCore
+ */
+ public function listObjectByDir($bucket, $dir = '/', $list_model = 2, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ $opt[self::BUCKET] = $bucket;
+ if (empty($opt[self::BUCKET])) {
+ throw new BCS_Exception("Bucket should not be empty, please check", -1);
+ }
+ $opt[self::METHOD] = 'GET';
+ $opt[self::OBJECT] = '/';
+ $opt[self::QUERY_STRING] = array();
+ if (isset($opt['start']) && is_int($opt['start'])) {
+ $opt[self::QUERY_STRING]['start'] = $opt['start'];
+ }
+ if (isset($opt['limit']) && is_int($opt['limit'])) {
+ $opt[self::QUERY_STRING]['limit'] = $opt['limit'];
+ }
+
+ $opt[self::QUERY_STRING]['prefix'] = rawurlencode($dir);
+ $opt[self::QUERY_STRING]['dir'] = $list_model;
+
+ $response = $this->authenticate($opt);
+ $this->log($response->isOK() ? "List object success!" : "Lit object failed! Response: [" . $response->body . "]", $opt);
+ return $response;
+ }
+
+ /**
+ * 上传文件
+ * @param string $bucket (Required)
+ * @param string $object (Required)
+ * @param string $file (Required); 需要上传的文件的文件路径
+ * @param array $opt (Optional)
+ * filename - Optional; 指定文件名
+ * acl - Optional ; 上传文件的acl,只能使用acl_type
+ * seekTo - Optional; 上传文件的偏移位置
+ * length - Optional; 待上传长度
+ * @return BCS_ResponseCore
+ */
+ public function createObject($bucket, $object, $file, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ $opt[self::BUCKET] = $bucket;
+ $opt[self::OBJECT] = $object;
+ $opt['fileUpload'] = $file;
+ $opt[self::METHOD] = 'PUT';
+ if (isset($opt['acl'])) {
+ if (in_array($opt['acl'], self::$ACL_TYPES)) {
+ self::setHeaderIntoOpt("x-bs-acl", $opt['acl'], $opt);
+ } else {
+ throw new BCS_Exception("Invalid acl string, it should be acl_type", -1);
+ }
+ unset($opt['acl']);
+ }
+ if (isset($opt['filename'])) {
+ self::setHeaderIntoOpt("Content-Disposition", 'attachment; filename=' . $opt['filename'], $opt);
+ }
+ $response = $this->authenticate($opt);
+ $this->log($response->isOK() ? "Create object[$object] file[$file] success!" : "Create object[$object] file[$file] failed! Response: [" . $response->body . "] Logid[" . $response->header["x-bs-request-id"] . "]", $opt);
+ return $response;
+ }
+
+ /**
+ * 上传文件
+ * @param string $bucket (Required)
+ * @param string $object (Required)
+ * @param string $file (Required); 需要上传的文件的文件路径
+ * @param array $opt (Optional)
+ * filename - Optional; 指定文件名
+ * acl - Optional ; 上传文件的acl,只能使用acl_type
+ * @return BCS_ResponseCore
+ */
+ public function createObjectByContent($bucket, $object, $content, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ $opt[self::BUCKET] = $bucket;
+ $opt[self::OBJECT] = $object;
+ $opt[self::METHOD] = 'PUT';
+ if (null !== $content && is_string($content)) {
+ $opt['content'] = $content;
+ } else {
+ throw new BCS_Exception("Invalid object content, please check.", -1);
+ }
+ if (isset($opt['acl'])) {
+ if (in_array($opt['acl'], self::$ACL_TYPES)) {
+ self::setHeaderIntoOpt("x-bs-acl", $opt['acl'], $opt);
+ } else {
+ throw new BCS_Exception("Invalid acl string, it should be acl_type", -1);
+ }
+ unset($opt['acl']);
+ }
+ if (isset($opt['filename'])) {
+ self::setHeaderIntoOpt("Content-Disposition", 'attachment; filename=' . $opt['filename'], $opt);
+ }
+ $response = $this->authenticate($opt);
+ $this->log($response->isOK() ? "Create object[$object] success!" : "Create object[$object] failed! Response: [" . $response->body . "] Logid[" . $response->header["x-bs-request-id"] . "]", $opt);
+ return $response;
+ }
+
+ /**
+ * 通过superfile的方式上传文件
+ * @param string $bucket (Required)
+ * @param string $object (Required)
+ * @param string $file (Required); 需要上传的文件的文件路径
+ * @param array $opt (Optional)
+ * filename - Optional; 指定文件名
+ * sub_object_size - Optional; 指定子文件的划分大小,单位B,建议以256KB为单位进行子object划分,默认为1MB进行划分
+ * @return BCS_ResponseCore
+ */
+ public function createObjectSuperfile($bucket, $object, $file, $opt = array())
+ {
+ if (isset($opt['length']) || isset($opt['seekTo'])) {
+ throw new BCS_Exception("Temporary unsupport opt of length and seekTo of superfile.", -1);
+ }
+ //$opt array
+ $this->assertParameterArray($opt);
+ $opt[self::BUCKET] = $bucket;
+ $opt['fileUpload'] = $file;
+ $opt[self::METHOD] = 'PUT';
+ if (isset($opt['acl'])) {
+ if (in_array($opt['acl'], self::$ACL_TYPES)) {
+ self::setHeaderIntoOpt("x-bs-acl", $opt['acl'], $opt);
+ } else {
+ throw new BCS_Exception("Invalid acl string, it should be acl_type", -1);
+ }
+ unset($opt['acl']);
+ }
+ //切片上传
+ if (!file_exists($opt['fileUpload'])) {
+ throw new BCS_Exception('File not found!', -1);
+ }
+ $fileSize = filesize($opt['fileUpload']);
+ $sub_object_size = 1024 * 1024; //default 1MB
+ if (defined("BCS_SUPERFILE_SLICE_SIZE")) {
+ $sub_object_size = BCS_SUPERFILE_SLICE_SIZE;
+ }
+ if (isset($opt["sub_object_size"])) {
+ if (is_int($opt["sub_object_size"]) && $opt["sub_object_size"] > 0) {
+ $sub_object_size = $opt["sub_object_size"];
+ } else {
+ throw new BCS_Exception("Param [sub_object_size] invalid ,please check!", -1);
+ }
+ }
+ $sliceNum = intval(ceil($fileSize / $sub_object_size));
+ $this->log("File[" . $opt['fileUpload'] . "], size=[$fileSize], sub_object_size=[$sub_object_size], sub_object_num=[$sliceNum]", $opt);
+ $object_list = array(
+ 'object_list' => array());
+ for ($i = 0; $i < $sliceNum; $i++) {
+ //send slice
+ $opt['seekTo'] = $i * $sub_object_size;
+
+ if (($i + 1) === $sliceNum) {
+ //last sub object
+ $opt['length'] = (0 === $fileSize % $sub_object_size) ? $sub_object_size : $fileSize % $sub_object_size;
+ } else {
+ $opt['length'] = $sub_object_size;
+ }
+ $opt[self::OBJECT] = $object . BCS_SUPERFILE_POSTFIX . $i;
+ $object_list['object_list']['part_' . $i] = array();
+ $object_list['object_list']['part_' . $i]['url'] = 'bs://' . $bucket . $opt[self::OBJECT];
+ $this->log("Begin to upload Sub-object[" . $opt[self::OBJECT] . "][$i/$sliceNum][seekto:" . $opt['seekTo'] . "][Length:" . $opt['length'] . "]", $opt);
+ $response = $this->createObject($bucket, $opt[self::OBJECT], $file, $opt);
+ if ($response->isOK()) {
+ $this->log("Sub-object upload[" . $opt[self::OBJECT] . "][$i/$sliceNum][seekto:" . $opt['seekTo'] . "][Length:" . $opt['length'] . "]success! ", $opt);
+ $object_list['object_list']['part_' . $i]['etag'] = $response->header['Content-MD5'];
+ continue;
+ } else {
+ $this->log("Sub-object upload[" . $opt[self::OBJECT] . "][$i/$sliceNum] failed! ", $opt);
+ return $response;
+ }
+ }
+ //将子文件分片的列表构造成 superfile
+ unset($opt['fileUpload']);
+ unset($opt['length']);
+ unset($opt['seekTo']);
+ $opt['content'] = self::arrayToJson($object_list);
+ $opt[self::QUERY_STRING] = array(
+ "superfile" => 1);
+ $opt[self::OBJECT] = $object;
+ if (isset($opt['filename'])) {
+ self::setHeaderIntoOpt("Content-Disposition", 'attachment; filename=' . $opt['filename'], $opt);
+ }
+ $response = $this->authenticate($opt);
+ $this->log($response->isOK() ? "Create object-superfile success!" : "Create object-superfile failed! Response: [" . $response->body . "]", $opt);
+ return $response;
+ }
+
+ /**
+ * 将目录中的所有文件进行上传,每个文件为单独object,object命名方式下详:
+ * 如有 /home/worker/a/b/c.txt 需上传目录为$dir=/home/worker/a
+ * object命令方式为
+ * 1. object默认命名方式为 “子目录名 +文件名”,如上述文件c.txt,默认为 '/b/c.txt'
+ * 2. 增强命名模式,在$opt中有可选参数进行配置
+ * 举例说明 :prefix . has_sub_directory?"/b":"" . '/c.txt'
+ * @param string $bucket (Required)
+ * @param string $dir (Required)
+ * @param array $opt(Optional)
+ * string prefix 文件object前缀
+ * boolean has_sub_directory(default=true) object命名中是否携带文件的子目录结构,若置为false,请确认待上传的目录和所有子目录中没有重名文件,否则会产生object覆盖问题
+ * BaiduBCS::IMPORT_BCS_PRE_FILTER 用户可自定义上传文件前的操作函数
+ * 1. 函数参数列表顺序需为 ($bucket,$object,$file,&$opt),注意$opt为upload_directory函数传入的$opt的拷贝,只对当前object生效
+ * 2. 函数返回值必须为boolean,当true该文件进行上传,若false跳过上传
+ * 3. 如果函数返回false,将不会进行post_filter的调用
+ * BaiduBCS::IMPORT_BCS_POST_FILTER 用户可自定义上传文件后的操作函数
+ * 1. 函数参数列表顺序需为 ($bucket,$object,$file,&$opt,$response),注意$opt为upload_directory函数传入的$opt的拷贝,只对当前object生效
+ * 2. 函数返回值无要求
+ * string seek_object 用户断点续传,需要为object名称,如果该object在目录中不存在,抛出异常,若存在则将该object和此后的object进行上传
+ * string seek_object_id 作用同seek_object,只需要传入上传过程中日志中展示的[a/b]中object序号即可,注意object序号是以1开始计算的
+ * @return array 数组形式的上传结果
+ * 'success' => int 上传成功的文件数目
+ * 'skipped' => int 被跳过的文件
+ * 'failed' => array() 上传失败的文件
+ *
+ */
+ public function uploadDirectory($bucket, $dir, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ if (!is_dir($dir)) {
+ throw new BCS_Exception("$dir is not a dir!", -1);
+ }
+ $result = array(
+ "success" => 0,
+ "failed" => array(),
+ "skipped" => 0);
+ $prefix = "";
+ if (isset($opt['prefix'])) {
+ $prefix = $opt['prefix'];
+ }
+ $has_sub_directory = true;
+ if (isset($opt['has_sub_directory']) && is_bool($opt['has_sub_directory'])) {
+ $has_sub_directory = $opt['has_sub_directory'];
+ }
+ //获取文件树和构造object名
+ $file_tree = self::getFiletree($dir);
+ $objects = array();
+ foreach ($file_tree as $file) {
+ $object = true == $has_sub_directory ? substr($file, strlen($dir)) : "/" . basename($file);
+ $objects[$prefix . $object] = $file;
+ }
+ $objectCount = count($objects);
+ $before_upload_log = "Upload directory: bucket[$bucket] upload_dir[$dir] file_sum[$objectCount]";
+ if (isset($opt["seek_object_id"])) {
+ $before_upload_log .= " seek_object_id[" . $opt["seek_object_id"] . "/$objectCount]";
+ }
+ if (isset($opt["seek_object"])) {
+ $before_upload_log .= " seek_object[" . $opt["seek_object"] . "]";
+ }
+ $this->log($before_upload_log, $opt);
+ //查看是否需要查询断点,进行断点续传
+ if (isset($opt["seek_object_id"]) && isset($opt["seek_object"])) {
+ throw new BCS_Exception("Can not set see_object_id and seek_object at the same time!", -1);
+ }
+
+ $num = 1;
+ if (isset($opt["seek_object"])) {
+ if (isset($objects[$opt["seek_object"]])) {
+ foreach ($objects as $object => $file) {
+ if ($object != $opt["seek_object"]) {
+ //当非断点文件,该object已完成上传
+ $this->log("Seeking[" . $opt["seek_object"] . "]. Skip id[$num/$objectCount]object[$object]file[$file].", $opt);
+ //$result ['skipped'] [] = "[$num/$objectCount] " . $file;
+ $result['skipped']++;
+ unset($objects[$object]);
+ } else {
+ //当找到断点文件,停止循环,从断点文件重新上传
+ //当非断点文件,该object已完成上传
+ $this->log("Found seek id[$num/$objectCount]object[$object]file[$file], begin from here.", $opt);
+ break;
+ }
+ $num++;
+ }
+ } else {
+ throw new BCS_Exception("Can not find you seek object, please check!", -1);
+ }
+ }
+ if (isset($opt["seek_object_id"])) {
+ if (is_int($opt["seek_object_id"]) && $opt["seek_object_id"] <= $objectCount) {
+ foreach ($objects as $object => $file) {
+ if ($num < $opt["seek_object_id"]) {
+ $this->log("Seeking object of [" . $opt["seek_object_id"] . "/$objectCount]. Skip id[$num/$objectCount]object[$object]file[$file].", $opt);
+ //$result ['skipped'] [] = "[$num/$objectCount] " . $file;
+ $result['skipped']++;
+ unset($objects[$object]);
+ } else {
+ break;
+ }
+ $num++;
+ }
+ } else {
+ throw new BCS_Exception("Param seek_object_id not valid, please check!", -1);
+ }
+ }
+ //上传objects
+ $objectCount = count($objects);
+ foreach ($objects as $object => $file) {
+ $tmp_opt = array_merge($opt);
+ if (isset($opt[self::IMPORT_BCS_PRE_FILTER]) && function_exists($opt[self::IMPORT_BCS_PRE_FILTER])) {
+ $bolRes = $opt[self::IMPORT_BCS_PRE_FILTER]($bucket, $object, $file, $tmp_opt);
+ if (true !== $bolRes) {
+ $this->log("User pre_filter_function return un-true. Skip id[$num/$objectCount]object[$object]file[$file].", $opt);
+ //$result ['skipped'] [] = "id[$num/$objectCount]object[$object]file[$file]";
+ $result['skipped']++;
+ $num++;
+ continue;
+ }
+ }
+ try {
+ $response = $this->createObject($bucket, $object, $file, $tmp_opt);
+ } catch (Exception $e) {
+ $this->log($e->getMessage(), $opt);
+ $this->log("Upload Failed id[$num/$objectCount]object[$object]file[$file].", $opt);
+ $num++;
+ continue;
+ }
+ if ($response->isOK()) {
+ $result["success"]++;
+ $this->log("Upload Success id[$num/$objectCount]object[$object]file[$file].", $opt);
+ } else {
+ $result["failed"][] = "id[$num/$objectCount]object[$object]file[$file]";
+ $this->log("Upload Failed id[$num/$objectCount]object[$object]file[$file].", $opt);
+ }
+ if (isset($opt[self::IMPORT_BCS_POST_FILTER]) && function_exists($opt[self::IMPORT_BCS_POST_FILTER])) {
+ $opt[self::IMPORT_BCS_POST_FILTER]($bucket, $object, $file, $tmp_opt, $response);
+ }
+ $num++;
+ }
+ //打印日志并返回结果数组
+ $result_str = "\r\n\r\nUpload $dir to $bucket finished!\r\n";
+ $result_str .= "**********************************************************\r\n";
+ $result_str .= "**********************Result Summary**********************\r\n";
+ $result_str .= "**********************************************************\r\n";
+ $result_str .= "Upload directory : [$dir]\r\n";
+ $result_str .= "File num : [$objectCount]\r\n";
+ $result_str .= "Success: \r\n\tNum: " . $result["success"] . "\r\n";
+ $result_str .= "Skipped:\r\n\tNum:" . $result["skipped"] . "\r\n";
+ // foreach ( $result ["skipped"] as $skip ) {
+ // $result_str .= "\t$skip\r\n";
+ // }
+ $result_str .= "Failed:\r\n\tNum:" . count($result["failed"]) . "\r\n";
+ foreach ($result["failed"] as $fail) {
+ $result_str .= "\t$fail\r\n";
+ }
+ if (isset($opt[self::IMPORT_BCS_LOG_METHOD])) {
+ $this->log($result_str, $opt);
+ } else {
+ echo $result_str;
+ }
+ return $result;
+ }
+
+ /**
+ * 通过此方法以拷贝的方式创建object,object来源为$source
+ * @param array $source (Required) object 来源
+ * bucket(Required)
+ * object(Required)
+ * @param array $dest (Required) 待拷贝的目标object
+ * bucket(Required)
+ * object(Required)
+ * @param array $opt (Optional)
+ * source_tag 指定拷贝对象的版本号
+ * @throws BCS_Exception
+ * @return BCS_ResponseCore
+ */
+ public function copyObject($source, $dest, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ //valid source and dest
+ if (empty($source) || !is_array($source) || !isset($source[self::BUCKET]) || !isset($source[self::OBJECT])) {
+ throw new BCS_Exception('$source invalid, please check!', -1);
+ }
+ if (empty($dest) || !is_array($dest) || !isset($dest[self::BUCKET]) || !isset($dest[self::OBJECT]) || !self::validateBucket($dest[self::BUCKET]) || !self::validateObject($dest[self::OBJECT])) {
+ throw new BCS_Exception('$dest invalid, please check!', -1);
+ }
+ $opt[self::BUCKET] = $dest[self::BUCKET];
+ $opt[self::OBJECT] = $dest[self::OBJECT];
+ $opt[self::METHOD] = 'PUT';
+ self::setHeaderIntoOpt('x-bs-copy-source', 'bs://' . $source[self::BUCKET] . $source[self::OBJECT], $opt);
+ if (isset($opt['source_tag'])) {
+ self::setHeaderIntoOpt('x-bs-copy-source-tag', $opt['source_tag'], $opt);
+ }
+ $response = $this->authenticate($opt);
+ $this->log($response->isOK() ? "Copy object success!" : "Copy object failed! Response: [" . $response->body . "]", $opt);
+ return $response;
+ }
+
+ /**
+ * 设置object的meta信息
+ * @param string $bucket (Required)
+ * @param string $object (Required)
+ * @param array $opt (Optional)
+ * 目前支持的meta信息如下:
+ * Content-Type
+ * Cache-Control
+ * Content-Disposition
+ * Content-Encoding
+ * Content-MD5
+ * Expires
+ * @return BCS_ResponseCore
+ */
+ public function setObjectMeta($bucket, $object, $meta, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ $this->assertParameterArray($meta);
+ $opt[self::BUCKET] = $bucket;
+ $opt[self::OBJECT] = $object;
+ $opt[self::METHOD] = 'PUT';
+ //利用copy_object接口来设置meta信息
+ $source = "bs://$bucket$object";
+ if (empty($meta)) {
+ throw new BCS_Exception('$meta can not be empty! And $meta must be array.', -1);
+ }
+ foreach ($meta as $header => $value) {
+ self::setHeaderIntoOpt($header, $value, $opt);
+ }
+ $source = array(
+ self::BUCKET => $bucket,
+ self::OBJECT => $object);
+ $response = $this->copyObject($source, $source, $opt);
+ $this->log($response->isOK() ? "Set object meta success!" : "Set object meta failed! Response: [" . $response->body . "]", $opt);
+ return $response;
+ }
+
+ /**
+ * 获取object的acl
+ * @param string $bucket (Required)
+ * @param string $object (Required)
+ * @param array $opt (Optional)
+ * @throws BCS_Exception
+ * @return BCS_ResponseCore
+ */
+ public function getObjectAcl($bucket, $object, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ $opt[self::BUCKET] = $bucket;
+ $opt[self::METHOD] = 'GET';
+ $opt[self::OBJECT] = $object;
+ $opt[self::QUERY_STRING] = array(
+ self::ACL => 1);
+ $response = $this->authenticate($opt);
+ $this->log($response->isOK() ? "Get object acl success!" : "Get object acl failed! Response: [" . $response->body . "]", $opt);
+ return $response;
+ }
+
+ /**
+ * 设置object的acl,有三种模式,
+ * (1).设置详细json格式的acl;
+ * a. $acl 为json的array
+ * b. $acl 为json的string
+ * (2).通过acl_type字段进行设置
+ * a. $acl 为BaiduBCS::$ACL_ACTIONS中的字段
+ * @param string $bucket (Required)
+ * @param string $object (Required)
+ * @param string|array $acl (Required)
+ * @param array $opt (Optional)
+ * @return BCS_ResponseCore
+ */
+ public function setObjectAcl($bucket, $object, $acl, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ //analyze acl
+ $result = $this->analyzeUserAcl($acl);
+ $opt = array_merge($opt, $result);
+ $opt[self::BUCKET] = $bucket;
+ $opt[self::METHOD] = 'PUT';
+ $opt[self::OBJECT] = $object;
+ $opt[self::QUERY_STRING] = array(
+ self::ACL => 1);
+ $response = $this->authenticate($opt);
+ $this->log($response->isOK() ? "Set object acl success!" : "Set object acl failed! Response: [" . $response->body . "]", $opt);
+ return $response;
+ }
+
+ /**
+ * 删除object
+ * @param string $bucket (Required)
+ * @param string $object (Required)
+ * @param array $opt (Optional)
+ * @throws BCS_Exception
+ * @return BCS_ResponseCore
+ */
+ public function deleteObject($bucket, $object, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ $opt[self::BUCKET] = $bucket;
+ $opt[self::METHOD] = 'DELETE';
+ $opt[self::OBJECT] = $object;
+ $response = $this->authenticate($opt);
+ $this->log($response->isOK() ? "Delete object success!" : "Delete object failed! Response: [" . $response->body . "]", $opt);
+ return $response;
+ }
+
+ /**
+ * 判断object是否存在
+ * @param string $bucket (Required)
+ * @param string $object (Required)
+ * @param array $opt (Optional)
+ * @throws BCS_Exception
+ * @return boolean true|boolean false|BCS_ResponseCore
+ * true:object存在
+ * false:不存在
+ * BCS_ResponseCore其他错误
+ */
+ public function isObjectExist($bucket, $object, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ $opt[self::BUCKET] = $bucket;
+ $opt[self::METHOD] = 'HEAD';
+ $opt[self::OBJECT] = $object;
+ $response = $this->getObjectInfo($bucket, $object, $opt);
+ if ($response->isOK()) {
+ return true;
+ } elseif (404 === $response->status) {
+ return false;
+ }
+ return $response;
+ }
+
+ /**
+ * 获取文件信息,发送的为HTTP HEAD请求,文件信息都在http response的header中,不会提取文件的内容
+ * @param string $bucket (Required)
+ * @param string $object (Required)
+ * @param array $opt (Optional)
+ * @throws BCS_Exception
+ * @return array BCS_ResponseCore
+ */
+ public function getObjectInfo($bucket, $object, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ $opt[self::BUCKET] = $bucket;
+ $opt[self::METHOD] = 'HEAD';
+ $opt[self::OBJECT] = $object;
+ $response = $this->authenticate($opt);
+ $this->log($response->isOK() ? "Get object info success!" : "Get object info failed! Response: [" . $response->body . "]", $opt);
+ return $response;
+ }
+
+ /**
+ * 下载object
+ * @param string $bucket (Required)
+ * @param string $object (Required)
+ * @param array $opt (Optional)
+ * fileWriteTo (Optional)直接将请求结果写入该文件,如果fileWriteTo文件存在,sdk进行重命名再存储
+ * @throws BCS_Exception
+ * @return BCS_ResponseCore
+ */
+ public function getObject($bucket, $object, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ //若fileWriteTo待写入的文件已经存在,需要进行重命名
+ if (isset($opt["fileWriteTo"]) && file_exists($opt["fileWriteTo"])) {
+ $original_file_write_to = $opt["fileWriteTo"];
+ $arr = explode(DIRECTORY_SEPARATOR, $opt["fileWriteTo"]);
+ $file_name = $arr[count($arr) - 1];
+ $num = 1;
+ while (file_exists($opt["fileWriteTo"])) {
+ $new_name_arr = explode(".", $file_name);
+ if (count($new_name_arr) > 1) {
+ $new_name_arr[count($new_name_arr) - 2] .= " ($num)";
+ } else {
+ $new_name_arr[0] .= " ($num)";
+ }
+ $arr[count($arr) - 1] = implode(".", $new_name_arr);
+ $opt["fileWriteTo"] = implode(DIRECTORY_SEPARATOR, $arr);
+ $num++;
+ }
+ $this->log("[$original_file_write_to] already exist, rename it to [" . $opt["fileWriteTo"] . "]", $opt);
+ }
+ $opt[self::BUCKET] = $bucket;
+ $opt[self::METHOD] = 'GET';
+ $opt[self::OBJECT] = $object;
+ $response = $this->authenticate($opt);
+ $this->log($response->isOK() ? "Get object success!" : "Get object failed! Response: [" . $response->body . "]", $opt);
+ if (!$response->isOK() && isset($opt["fileWriteTo"])) {
+ unlink($opt["fileWriteTo"]);
+ }
+ return $response;
+ }
+
+ /**
+ * 生成签名链接
+ */
+ private function generateUserUrl($method, $bucket, $object, $opt = array())
+ {
+ $opt[self::AK] = $this->ak;
+ $opt[self::SK] = $this->sk;
+ $opt[self::BUCKET] = $bucket;
+ $opt[self::METHOD] = $method;
+ $opt[self::OBJECT] = $object;
+ $opt[self::QUERY_STRING] = array();
+ if (isset($opt["time"])) {
+ $opt[self::QUERY_STRING]["time"] = $opt["time"];
+ }
+ if (isset($opt["size"])) {
+ $opt[self::QUERY_STRING]["size"] = $opt["size"];
+ }
+ return $this->formatUrl($opt);
+ }
+
+ /**
+ * 生成get_object的url
+ * @param string $bucket (Required)
+ * @param string $object (Required)
+ * return false| string url
+ */
+ public function generateGetObjectUrl($bucket, $object, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ return $this->generateUserUrl("GET", $bucket, $object, $opt);
+ }
+
+ /**
+ * 生成put_object的url
+ * @param string $bucket (Required)
+ * @param string $object (Required)
+ * return false| string url
+ */
+ public function generatePutObjectUrl($bucket, $object, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ return $this->generateUserUrl("PUT", $bucket, $object, $opt);
+ }
+
+ /**
+ * 生成post_object的url
+ * @param string $bucket (Required)
+ * @param string $object (Required)
+ * return false| string url
+ */
+ public function generatePostObjectUrl($bucket, $object, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ return $this->generateUserUrl("POST", $bucket, $object, $opt);
+ }
+
+ /**
+ * 生成delete_object的url
+ * @param string $bucket (Required)
+ * @param string $object (Required)
+ * return false| string url
+ */
+ public function generateDeleteObjectUrl($bucket, $object, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ return $this->generateUserUrl("DELETE", $bucket, $object, $opt);
+ }
+
+ /**
+ * 生成head_object的url
+ * @param string $bucket (Required)
+ * @param string $object (Required)
+ * return false| string url
+ */
+ public function generateHeadObjectUrl($bucket, $object, $opt = array())
+ {
+ $this->assertParameterArray($opt);
+ return $this->generateUserUrl("HEAD", $bucket, $object, $opt);
+ }
+
+ /**
+ * @return the $use_ssl
+ */
+ public function getuseSsl()
+ {
+ return $this->use_ssl;
+ }
+
+ /**
+ * @param boolean $use_ssl
+ */
+ public function setuseSsl($use_ssl)
+ {
+ $this->use_ssl = $use_ssl;
+ }
+
+ /**
+ * 校验bucket是否合法,bucket规范
+ * 1. 由小写字母,数字和横线'-'组成,长度为6~63位
+ * 2. 不能以数字作为Bucket开头
+ * 3. 不能以'-'作为Bucket的开头或者结尾
+ * @param string $bucket
+ * @return boolean
+ */
+ public static function validateBucket($bucket)
+ {
+ //bucket 正则
+ $pattern1 = '/^[a-z][-a-z0-9]{4,61}[a-z0-9]$/';
+ if (!preg_match($pattern1, $bucket)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 校验object是否合法,object命名规范
+ * 1. object必须以'/'开头
+ * @param string $object
+ * @return boolean
+ */
+ public static function validateObject($object)
+ {
+ $pattern = '/^\//';
+ if (empty($object) || !preg_match($pattern, $object)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 将常用set http-header的动作抽离出来
+ * @param string $header
+ * @param string $value
+ * @param array $opt
+ * @throws BCS_Exception
+ * @return void
+ */
+ private static function setHeaderIntoOpt($header, $value, &$opt)
+ {
+ if (isset($opt[self::HEADERS])) {
+ if (!is_array($opt[self::HEADERS])) {
+ trigger_error('Invalid $opt[\'headers\'], please check.');
+ throw new BCS_Exception('Invalid $opt[\'headers\'], please check.', -1);
+ }
+ } else {
+ $opt[self::HEADERS] = array();
+ }
+ $opt[self::HEADERS][$header] = $value;
+ }
+
+ /**
+ * 使用特定function对数组中所有元素做处理
+ * @param string &$array 要处理的字符串
+ * @param string $function 要执行的函数
+ * @param boolean $apply_to_keys_also 是否也应用到key上
+ */
+ private static function arrayRecursive(&$array, $function, $apply_to_keys_also = false)
+ {
+ foreach ($array as $key => $value) {
+ if (is_array($value)) {
+ self::arrayRecursive($array[$key], $function, $apply_to_keys_also);
+ } else {
+ $array[$key] = $function($value);
+ }
+
+ if ($apply_to_keys_also && is_string($key)) {
+ $new_key = $function($key);
+ if ($new_key != $key) {
+ $array[$new_key] = $array[$key];
+ unset($array[$key]);
+ }
+ }
+ }
+ }
+
+ /**
+ * 由数组构造json字符串,增加了一些特殊处理以支持特殊字符和不同编码的中文
+ * @param array $array
+ */
+ private static function arrayToJson($array)
+ {
+ if (!is_array($array)) {
+ throw new BCS_Exception("Param must be array in function array_to_json()", -1);
+ }
+ self::arrayRecursive($array, 'addslashes', false);
+ self::arrayRecursive($array, 'rawurlencode', false);
+ return rawurldecode(json_encode($array));
+ }
+
+ /**
+ * 根据用户传入的acl,进行相应的处理
+ * (1).设置详细json格式的acl;
+ * a. $acl 为json的array
+ * b. $acl 为json的string
+ * (2).通过acl_type字段进行设置
+ * @param string|array $acl
+ * @throws BCS_Exception
+ * @return array
+ */
+ private function analyzeUserAcl($acl)
+ {
+ $result = array();
+ if (is_array($acl)) {
+ //(1).a
+ $result['content'] = $this->checkUserAcl($acl);
+ } else if (is_string($acl)) {
+ if (in_array($acl, self::$ACL_TYPES)) {
+ //(2).a
+ $result["headers"] = array(
+ "x-bs-acl" => $acl);
+ } else {
+ //(1).b
+ $result['content'] = $acl;
+ }
+ } else {
+ throw new BCS_Exception("Invalid acl.", -1);
+ }
+ return $result;
+ }
+
+ /**
+ * 生成签名
+ * @param array $opt
+ * @return boolean|string
+ */
+ private function formatSignature($opt)
+ {
+ $flags = "";
+ $content = '';
+ if (!isset($opt[self::AK]) || !isset($opt[self::SK])) {
+ trigger_error('ak or sk is not in the array when create factor!');
+ return false;
+ }
+ if (isset($opt[self::BUCKET]) && isset($opt[self::METHOD]) && isset($opt[self::OBJECT])) {
+ $flags .= 'MBO';
+ $content .= "Method=" . $opt[self::METHOD] . "\n"; //method
+ $content .= "Bucket=" . $opt[self::BUCKET] . "\n"; //bucket
+ $content .= "Object=" . self::trimUrl($opt[self::OBJECT]) . "\n"; //object
+ } else {
+ trigger_error('bucket、method and object cann`t be NULL!');
+ return false;
+ }
+ if (isset($opt['ip'])) {
+ $flags .= 'I';
+ $content .= "Ip=" . $opt['ip'] . "\n";
+ }
+ if (isset($opt['time'])) {
+ $flags .= 'T';
+ $content .= "Time=" . $opt['time'] . "\n";
+ }
+ if (isset($opt['size'])) {
+ $flags .= 'S';
+ $content .= "Size=" . $opt['size'] . "\n";
+ }
+ $content = $flags . "\n" . $content;
+ $sign = base64_encode(hash_hmac('sha1', $content, $opt[self::SK], true));
+ return 'sign=' . $flags . ':' . $opt[self::AK] . ':' . urlencode($sign);
+ }
+
+ /**
+ * 检查用户输入的acl array是否合法,并转为json
+ * @param array $acl
+ * @throws BCS_Exception
+ * @return string acl-json
+ */
+ private function checkUserAcl($acl)
+ {
+ if (!is_array($acl)) {
+ throw new BCS_Exception("Invalid acl array");
+ }
+ foreach ($acl['statements'] as $key => $statement) {
+ // user resource action effect must in statement
+ if (!isset($statement['user']) || !isset($statement['resource']) || !isset($statement['action']) || !isset($statement['effect'])) {
+ throw new BCS_Exception('Param miss: format acl error, please check your param!');
+ }
+ if (!is_array($statement['user']) || !is_array($statement['resource'])) {
+ throw new BCS_Exception('Param error: user or resource must be array, please check your param!');
+ }
+ if (!is_array($statement['action']) || !count(array_diff($statement['action'], self::$ACL_ACTIONS)) == 0) {
+ throw new BCS_Exception('Param error: action, please check your param!');
+ }
+ if (!in_array($statement['effect'], self::$ACL_EFFECTS)) {
+ throw new BCS_Exception('Param error: effect, please check your param!');
+ }
+ if (isset($statement['time'])) {
+ if (!is_array($statement['time'])) {
+ throw new BCS_Exception('Param error: time, please check your param!');
+ }
+ }
+ }
+
+ return self::arrayToJson($acl);
+ }
+
+ /**
+ * 构造url
+ * @param array $opt
+ * @return boolean|string
+ */
+ private function formatUrl($opt)
+ {
+ $sign = $this->formatSignature($opt);
+ if (false === $sign) {
+ trigger_error("Format signature failed, please check!");
+ return false;
+ }
+ $opt['sign'] = $sign;
+ $url = "";
+ $url .= $this->use_ssl ? 'https://' : 'http://';
+ $url .= $this->hostname;
+ $url .= '/' . $opt[self::BUCKET];
+ if (isset($opt[self::OBJECT]) && '/' !== $opt[self::OBJECT]) {
+ $url .= "/" . rawurlencode($opt[self::OBJECT]);
+ }
+ $url .= '?' . $sign;
+ if (isset($opt[self::QUERY_STRING])) {
+ foreach ($opt[self::QUERY_STRING] as $key => $value) {
+ $url .= '&' . $key . '=' . $value;
+ }
+ }
+ return $url;
+ }
+
+ /**
+ * 将url中 '//' 替换为 '/'
+ * @param $url
+ * @return string
+ */
+ public static function trimUrl($url)
+ {
+ $result = str_replace("//", "/", $url);
+ while ($result !== $url) {
+ $url = $result;
+ $result = str_replace("//", "/", $url);
+ }
+ return $result;
+ }
+
+ /**
+ * 获取传入目录的文件列表
+ * @param string $dir 文件目录
+ * @return array 文件树
+ */
+ public static function getFiletree($dir, $file_prefix = "/*")
+ {
+ $tree = array();
+ foreach (glob($dir . $file_prefix) as $single) {
+ if (is_dir($single)) {
+ $tree = array_merge($tree, self::getFiletree($single));
+ } else {
+ $tree[] = $single;
+ }
+ }
+ return $tree;
+ }
+
+ /**
+ * 内置的日志函数,可以根据用户传入的log函数,进行日志输出
+ * @param string $log
+ * @param array $opt
+ */
+ public function log($log, $opt)
+ {
+ if (isset($opt[self::IMPORT_BCS_LOG_METHOD]) && function_exists($opt[self::IMPORT_BCS_LOG_METHOD])) {
+ $opt[self::IMPORT_BCS_LOG_METHOD]($log);
+ } else {
+ trigger_error($log);
+ }
+ }
+
+ /**
+ * make sure $opt is an array
+ * @param $opt
+ */
+ private function assertParameterArray($opt)
+ {
+ if (!is_array($opt)) {
+ throw new BCS_Exception('Parameter must be array, please check!', -1);
+ }
+ }
+}
diff --git a/Framework/Library/Think/Upload/Driver/Bcs/mimetypes.class.php b/Framework/Library/Think/Upload/Driver/Bcs/mimetypes.class.php
new file mode 100644
index 00000000..a9d0961c
--- /dev/null
+++ b/Framework/Library/Think/Upload/Driver/Bcs/mimetypes.class.php
@@ -0,0 +1,140 @@
+ 'video/3gpp', 'ai' => 'application/postscript',
+ 'aif' => 'audio/x-aiff', 'aifc' => 'audio/x-aiff',
+ 'aiff' => 'audio/x-aiff', 'asc' => 'text/plain',
+ 'atom' => 'application/atom+xml', 'au' => 'audio/basic',
+ 'avi' => 'video/x-msvideo', 'bcpio' => 'application/x-bcpio',
+ 'bin' => 'application/octet-stream', 'bmp' => 'image/bmp',
+ 'cdf' => 'application/x-netcdf', 'cgm' => 'image/cgm',
+ 'class' => 'application/octet-stream',
+ 'cpio' => 'application/x-cpio',
+ 'cpt' => 'application/mac-compactpro',
+ 'csh' => 'application/x-csh', 'css' => 'text/css',
+ 'dcr' => 'application/x-director', 'dif' => 'video/x-dv',
+ 'dir' => 'application/x-director', 'djv' => 'image/vnd.djvu',
+ 'djvu' => 'image/vnd.djvu',
+ 'dll' => 'application/octet-stream',
+ 'dmg' => 'application/octet-stream',
+ 'dms' => 'application/octet-stream',
+ 'doc' => 'application/msword', 'dtd' => 'application/xml-dtd',
+ 'dv' => 'video/x-dv', 'dvi' => 'application/x-dvi',
+ 'dxr' => 'application/x-director',
+ 'eps' => 'application/postscript', 'etx' => 'text/x-setext',
+ 'exe' => 'application/octet-stream',
+ 'ez' => 'application/andrew-inset', 'flv' => 'video/x-flv',
+ 'gif' => 'image/gif', 'gram' => 'application/srgs',
+ 'grxml' => 'application/srgs+xml',
+ 'gtar' => 'application/x-gtar', 'gz' => 'application/x-gzip',
+ 'hdf' => 'application/x-hdf',
+ 'hqx' => 'application/mac-binhex40', 'htm' => 'text/html',
+ 'html' => 'text/html', 'ice' => 'x-conference/x-cooltalk',
+ 'ico' => 'image/x-icon', 'ics' => 'text/calendar',
+ 'ief' => 'image/ief', 'ifb' => 'text/calendar',
+ 'iges' => 'model/iges', 'igs' => 'model/iges',
+ 'jnlp' => 'application/x-java-jnlp-file', 'jp2' => 'image/jp2',
+ 'jpe' => 'image/jpeg', 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg', 'js' => 'application/x-javascript',
+ 'kar' => 'audio/midi', 'latex' => 'application/x-latex',
+ 'lha' => 'application/octet-stream',
+ 'lzh' => 'application/octet-stream',
+ 'm3u' => 'audio/x-mpegurl', 'm4a' => 'audio/mp4a-latm',
+ 'm4p' => 'audio/mp4a-latm', 'm4u' => 'video/vnd.mpegurl',
+ 'm4v' => 'video/x-m4v', 'mac' => 'image/x-macpaint',
+ 'man' => 'application/x-troff-man',
+ 'mathml' => 'application/mathml+xml',
+ 'me' => 'application/x-troff-me', 'mesh' => 'model/mesh',
+ 'mid' => 'audio/midi', 'midi' => 'audio/midi',
+ 'mif' => 'application/vnd.mif', 'mov' => 'video/quicktime',
+ 'movie' => 'video/x-sgi-movie', 'mp2' => 'audio/mpeg',
+ 'mp3' => 'audio/mpeg', 'mp4' => 'video/mp4',
+ 'mpe' => 'video/mpeg', 'mpeg' => 'video/mpeg',
+ 'mpg' => 'video/mpeg', 'mpga' => 'audio/mpeg',
+ 'ms' => 'application/x-troff-ms', 'msh' => 'model/mesh',
+ 'mxu' => 'video/vnd.mpegurl', 'nc' => 'application/x-netcdf',
+ 'oda' => 'application/oda', 'ogg' => 'application/ogg',
+ 'ogv' => 'video/ogv', 'pbm' => 'image/x-portable-bitmap',
+ 'pct' => 'image/pict', 'pdb' => 'chemical/x-pdb',
+ 'pdf' => 'application/pdf',
+ 'pgm' => 'image/x-portable-graymap',
+ 'pgn' => 'application/x-chess-pgn', 'pic' => 'image/pict',
+ 'pict' => 'image/pict', 'png' => 'image/png',
+ 'pnm' => 'image/x-portable-anymap',
+ 'pnt' => 'image/x-macpaint', 'pntg' => 'image/x-macpaint',
+ 'ppm' => 'image/x-portable-pixmap',
+ 'ppt' => 'application/vnd.ms-powerpoint',
+ 'ps' => 'application/postscript', 'qt' => 'video/quicktime',
+ 'qti' => 'image/x-quicktime', 'qtif' => 'image/x-quicktime',
+ 'ra' => 'audio/x-pn-realaudio',
+ 'ram' => 'audio/x-pn-realaudio', 'ras' => 'image/x-cmu-raster',
+ 'rdf' => 'application/rdf+xml', 'rgb' => 'image/x-rgb',
+ 'rm' => 'application/vnd.rn-realmedia',
+ 'roff' => 'application/x-troff', 'rtf' => 'text/rtf',
+ 'rtx' => 'text/richtext', 'sgm' => 'text/sgml',
+ 'sgml' => 'text/sgml', 'sh' => 'application/x-sh',
+ 'shar' => 'application/x-shar', 'silo' => 'model/mesh',
+ 'sit' => 'application/x-stuffit',
+ 'skd' => 'application/x-koan', 'skm' => 'application/x-koan',
+ 'skp' => 'application/x-koan', 'skt' => 'application/x-koan',
+ 'smi' => 'application/smil', 'smil' => 'application/smil',
+ 'snd' => 'audio/basic', 'so' => 'application/octet-stream',
+ 'spl' => 'application/x-futuresplash',
+ 'src' => 'application/x-wais-source',
+ 'sv4cpio' => 'application/x-sv4cpio',
+ 'sv4crc' => 'application/x-sv4crc', 'svg' => 'image/svg+xml',
+ 'swf' => 'application/x-shockwave-flash',
+ 't' => 'application/x-troff', 'tar' => 'application/x-tar',
+ 'tcl' => 'application/x-tcl', 'tex' => 'application/x-tex',
+ 'texi' => 'application/x-texinfo',
+ 'texinfo' => 'application/x-texinfo', 'tif' => 'image/tiff',
+ 'tiff' => 'image/tiff', 'tr' => 'application/x-troff',
+ 'tsv' => 'text/tab-separated-values', 'txt' => 'text/plain',
+ 'ustar' => 'application/x-ustar',
+ 'vcd' => 'application/x-cdlink', 'vrml' => 'model/vrml',
+ 'vxml' => 'application/voicexml+xml', 'wav' => 'audio/x-wav',
+ 'wbmp' => 'image/vnd.wap.wbmp',
+ 'wbxml' => 'application/vnd.wap.wbxml', 'webm' => 'video/webm',
+ 'wml' => 'text/vnd.wap.wml',
+ 'wmlc' => 'application/vnd.wap.wmlc',
+ 'wmls' => 'text/vnd.wap.wmlscript',
+ 'wmlsc' => 'application/vnd.wap.wmlscriptc',
+ 'wmv' => 'video/x-ms-wmv', 'wrl' => 'model/vrml',
+ 'xbm' => 'image/x-xbitmap', 'xht' => 'application/xhtml+xml',
+ 'xhtml' => 'application/xhtml+xml',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xml' => 'application/xml', 'xpm' => 'image/x-xpixmap',
+ 'xsl' => 'application/xml', 'xslt' => 'application/xslt+xml',
+ 'xul' => 'application/vnd.mozilla.xul+xml',
+ 'xwd' => 'image/x-xwindowdump', 'xyz' => 'chemical/x-xyz',
+ 'zip' => 'application/zip',
+ //add by zhengkan 20110905
+ "apk" => "application/vnd.android.package-archive",
+ "bin" => "application/octet-stream",
+ "cab" => "application/vnd.ms-cab-compressed",
+ "gb" => "application/chinese-gb",
+ "gba" => "application/octet-stream",
+ "gbc" => "application/octet-stream",
+ "jad" => "text/vnd.sun.j2me.app-descriptor",
+ "jar" => "application/java-archive",
+ "nes" => "application/octet-stream",
+ "rar" => "application/x-rar-compressed",
+ "sis" => "application/vnd.symbian.install",
+ "sisx" => "x-epoc/x-sisx-app",
+ "smc" => "application/octet-stream",
+ "smd" => "application/octet-stream",
+ "swf" => "application/x-shockwave-flash",
+ "zip" => "application/x-zip-compressed",
+ "wap" => "text/vnd.wap.wml wml", "mrp" => "application/mrp",
+ //add by zhengkan 20110914
+ "wma" => "audio/x-ms-wma",
+ "lrc" => "application/lrc");
+ public static function getMimetype($ext)
+ {
+ $ext = strtolower($ext);
+ return (isset(self::$mime_types[$ext]) ? self::$mime_types[$ext] : 'application/octet-stream');
+ }
+}
diff --git a/Framework/Library/Think/Upload/Driver/Bcs/requestcore.class.php b/Framework/Library/Think/Upload/Driver/Bcs/requestcore.class.php
new file mode 100644
index 00000000..4f811021
--- /dev/null
+++ b/Framework/Library/Think/Upload/Driver/Bcs/requestcore.class.php
@@ -0,0 +1,879 @@
+).
+ */
+ public $request_class = 'BCS_RequestCore';
+ /**
+ * The default class to use for HTTP Responses (defaults to ).
+ */
+ public $response_class = 'BCS_ResponseCore';
+ /**
+ * Default useragent string to use.
+ */
+ public $useragent = 'BCS_RequestCore/1.4.2';
+ /**
+ * File to read from while streaming up.
+ */
+ public $read_file = null;
+ /**
+ * The resource to read from while streaming up.
+ */
+ public $read_stream = null;
+ /**
+ * The size of the stream to read from.
+ */
+ public $read_stream_size = null;
+ /**
+ * The length already read from the stream.
+ */
+ public $read_stream_read = 0;
+ /**
+ * File to write to while streaming down.
+ */
+ public $write_file = null;
+ /**
+ * The resource to write to while streaming down.
+ */
+ public $write_stream = null;
+ /**
+ * Stores the intended starting seek position.
+ */
+ public $seek_position = null;
+ /**
+ * The user-defined callback function to call when a stream is read from.
+ */
+ public $registered_streaming_read_callback = null;
+ /**
+ * The user-defined callback function to call when a stream is written to.
+ */
+ public $registered_streaming_write_callback = null;
+ /*%******************************************************************************************%*/
+ // CONSTANTS
+ /**
+ * GET HTTP Method
+ */
+ const HTTP_GET = 'GET';
+ /**
+ * POST HTTP Method
+ */
+ const HTTP_POST = 'POST';
+ /**
+ * PUT HTTP Method
+ */
+ const HTTP_PUT = 'PUT';
+ /**
+ * DELETE HTTP Method
+ */
+ const HTTP_DELETE = 'DELETE';
+ /**
+ * HEAD HTTP Method
+ */
+ const HTTP_HEAD = 'HEAD';
+
+ /*%******************************************************************************************%*/
+ // CONSTRUCTOR/DESTRUCTOR
+ /**
+ * Constructs a new instance of this class.
+ *
+ * @param string $url (Optional) The URL to request or service endpoint to query.
+ * @param string $proxy (Optional) The faux-url to use for proxy settings. Takes the following format: `proxy://user:pass@hostname:port`
+ * @param array $helpers (Optional) An associative array of classnames to use for request, and response functionality. Gets passed in automatically by the calling class.
+ * @return $this A reference to the current instance.
+ */
+ public function __construct($url = null, $proxy = null, $helpers = null)
+ {
+ // Set some default values.
+ $this->request_url = $url;
+ $this->method = self::HTTP_GET;
+ $this->request_headers = array();
+ $this->request_body = '';
+ // Set a new Request class if one was set.
+ if (isset($helpers['request']) && !empty($helpers['request'])) {
+ $this->request_class = $helpers['request'];
+ }
+ // Set a new Request class if one was set.
+ if (isset($helpers['response']) && !empty($helpers['response'])) {
+ $this->response_class = $helpers['response'];
+ }
+ if ($proxy) {
+ $this->setProxy($proxy);
+ }
+ return $this;
+ }
+
+ /**
+ * Destructs the instance. Closes opened file handles.
+ *
+ * @return $this A reference to the current instance.
+ */
+ public function __destruct()
+ {
+ if (isset($this->read_file) && isset($this->read_stream)) {
+ fclose($this->read_stream);
+ }
+ if (isset($this->write_file) && isset($this->write_stream)) {
+ fclose($this->write_stream);
+ }
+ return $this;
+ }
+
+ /*%******************************************************************************************%*/
+ // REQUEST METHODS
+ /**
+ * Sets the credentials to use for authentication.
+ *
+ * @param string $user (Required) The username to authenticate with.
+ * @param string $pass (Required) The password to authenticate with.
+ * @return $this A reference to the current instance.
+ */
+ public function setCredentials($user, $pass)
+ {
+ $this->username = $user;
+ $this->password = $pass;
+ return $this;
+ }
+
+ /**
+ * Adds a custom HTTP header to the cURL request.
+ *
+ * @param string $key (Required) The custom HTTP header to set.
+ * @param mixed $value (Required) The value to assign to the custom HTTP header.
+ * @return $this A reference to the current instance.
+ */
+ public function addHeader($key, $value)
+ {
+ $this->request_headers[$key] = $value;
+ return $this;
+ }
+
+ /**
+ * Removes an HTTP header from the cURL request.
+ *
+ * @param string $key (Required) The custom HTTP header to set.
+ * @return $this A reference to the current instance.
+ */
+ public function removeHeader($key)
+ {
+ if (isset($this->request_headers[$key])) {
+ unset($this->request_headers[$key]);
+ }
+ return $this;
+ }
+
+ /**
+ * Set the method type for the request.
+ *
+ * @param string $method (Required) One of the following constants: , , , , .
+ * @return $this A reference to the current instance.
+ */
+ public function setMethod($method)
+ {
+ $this->method = strtoupper($method);
+ return $this;
+ }
+
+ /**
+ * Sets a custom useragent string for the class.
+ *
+ * @param string $ua (Required) The useragent string to use.
+ * @return $this A reference to the current instance.
+ */
+ public function setUseragent($ua)
+ {
+ $this->useragent = $ua;
+ return $this;
+ }
+
+ /**
+ * Set the body to send in the request.
+ *
+ * @param string $body (Required) The textual content to send along in the body of the request.
+ * @return $this A reference to the current instance.
+ */
+ public function setBody($body)
+ {
+ $this->request_body = $body;
+ return $this;
+ }
+
+ /**
+ * Set the URL to make the request to.
+ *
+ * @param string $url (Required) The URL to make the request to.
+ * @return $this A reference to the current instance.
+ */
+ public function setRequestUrl($url)
+ {
+ $this->request_url = $url;
+ return $this;
+ }
+
+ /**
+ * Set additional CURLOPT settings. These will merge with the default settings, and override if
+ * there is a duplicate.
+ *
+ * @param array $curlopts (Optional) A set of key-value pairs that set `CURLOPT` options. These will merge with the existing CURLOPTs, and ones passed here will override the defaults. Keys should be the `CURLOPT_*` constants, not strings.
+ * @return $this A reference to the current instance.
+ */
+ public function setCurlopts($curlopts)
+ {
+ $this->curlopts = $curlopts;
+ return $this;
+ }
+
+ /**
+ * Sets the length in bytes to read from the stream while streaming up.
+ *
+ * @param integer $size (Required) The length in bytes to read from the stream.
+ * @return $this A reference to the current instance.
+ */
+ public function setReadStreamSize($size)
+ {
+ $this->read_stream_size = $size;
+ return $this;
+ }
+
+ /**
+ * Sets the resource to read from while streaming up. Reads the stream from its current position until
+ * EOF or `$size` bytes have been read. If `$size` is not given it will be determined by and
+ * .
+ *
+ * @param resource $resource (Required) The readable resource to read from.
+ * @param integer $size (Optional) The size of the stream to read.
+ * @return $this A reference to the current instance.
+ */
+ public function setReadStream($resource, $size = null)
+ {
+ if (!isset($size) || $size < 0) {
+ $stats = fstat($resource);
+ if ($stats && $stats['size'] >= 0) {
+ $position = ftell($resource);
+ if (false !== $position && $position >= 0) {
+ $size = $stats['size'] - $position;
+ }
+ }
+ }
+ $this->read_stream = $resource;
+ return $this->setReadStreamSize($size);
+ }
+
+ /**
+ * Sets the file to read from while streaming up.
+ *
+ * @param string $location (Required) The readable location to read from.
+ * @return $this A reference to the current instance.
+ */
+ public function setReadFile($location)
+ {
+ $this->read_file = $location;
+ $read_file_handle = fopen($location, 'r');
+ return $this->setReadStream($read_file_handle);
+ }
+
+ /**
+ * Sets the resource to write to while streaming down.
+ *
+ * @param resource $resource (Required) The writeable resource to write to.
+ * @return $this A reference to the current instance.
+ */
+ public function setWriteStream($resource)
+ {
+ $this->write_stream = $resource;
+ return $this;
+ }
+
+ /**
+ * Sets the file to write to while streaming down.
+ *
+ * @param string $location (Required) The writeable location to write to.
+ * @return $this A reference to the current instance.
+ */
+ public function setWriteFile($location)
+ {
+ $this->write_file = $location;
+ $write_file_handle = fopen($location, 'w');
+ return $this->setWriteStream($write_file_handle);
+ }
+
+ /**
+ * Set the proxy to use for making requests.
+ *
+ * @param string $proxy (Required) The faux-url to use for proxy settings. Takes the following format: `proxy://user:pass@hostname:port`
+ * @return $this A reference to the current instance.
+ */
+ public function setProxy($proxy)
+ {
+ $proxy = parse_url($proxy);
+ $proxy['user'] = isset($proxy['user']) ? $proxy['user'] : null;
+ $proxy['pass'] = isset($proxy['pass']) ? $proxy['pass'] : null;
+ $proxy['port'] = isset($proxy['port']) ? $proxy['port'] : null;
+ $this->proxy = $proxy;
+ return $this;
+ }
+
+ /**
+ * Set the intended starting seek position.
+ *
+ * @param integer $position (Required) The byte-position of the stream to begin reading from.
+ * @return $this A reference to the current instance.
+ */
+ public function setSeekPosition($position)
+ {
+ $this->seek_position = isset($position) ? (integer) $position : null;
+ return $this;
+ }
+
+ /**
+ * Register a callback function to execute whenever a data stream is read from using
+ * .
+ *
+ * The user-defined callback function should accept three arguments:
+ *
+ *
+ * $curl_handle
- resource
- Required - The cURL handle resource that represents the in-progress transfer.
+ * $file_handle
- resource
- Required - The file handle resource that represents the file on the local file system.
+ * $length
- integer
- Required - The length in kilobytes of the data chunk that was transferred.
+ *
+ *
+ * @param string|array|function $callback (Required) The callback function is called by , so you can pass the following values:
+ * The name of a global function to execute, passed as a string.
+ * A method to execute, passed as array('ClassName', 'MethodName')
.
+ * An anonymous function (PHP 5.3+).
+ * @return $this A reference to the current instance.
+ */
+ public function registerStreamingReadCallback($callback)
+ {
+ $this->registered_streaming_read_callback = $callback;
+ return $this;
+ }
+
+ /**
+ * Register a callback function to execute whenever a data stream is written to using
+ * .
+ *
+ * The user-defined callback function should accept two arguments:
+ *
+ *
+ * $curl_handle
- resource
- Required - The cURL handle resource that represents the in-progress transfer.
+ * $length
- integer
- Required - The length in kilobytes of the data chunk that was transferred.
+ *
+ *
+ * @param string|array|function $callback (Required) The callback function is called by , so you can pass the following values:
+ * The name of a global function to execute, passed as a string.
+ * A method to execute, passed as array('ClassName', 'MethodName')
.
+ * An anonymous function (PHP 5.3+).
+ * @return $this A reference to the current instance.
+ */
+ public function registerStreamingWriteCallback($callback)
+ {
+ $this->registered_streaming_write_callback = $callback;
+ return $this;
+ }
+
+ /*%******************************************************************************************%*/
+ // PREPARE, SEND, AND PROCESS REQUEST
+ /**
+ * A callback function that is invoked by cURL for streaming up.
+ *
+ * @param resource $curl_handle (Required) The cURL handle for the request.
+ * @param resource $file_handle (Required) The open file handle resource.
+ * @param integer $length (Required) The maximum number of bytes to read.
+ * @return binary Binary data from a stream.
+ */
+ public function streamingReadCallback($curl_handle, $file_handle, $length)
+ {
+ // Once we've sent as much as we're supposed to send...
+ if ($this->read_stream_read >= $this->read_stream_size) {
+ // Send EOF
+ return '';
+ }
+ // If we're at the beginning of an upload and need to seek...
+ if (0 == $this->read_stream_read && isset($this->seek_position) && ftell($this->read_stream) !== $this->seek_position) {
+ if (fseek($this->read_stream, $this->seek_position) !== 0) {
+ throw new BCS_RequestCore_Exception('The stream does not support seeking and is either not at the requested position or the position is unknown.');
+ }
+ }
+ $read = fread($this->read_stream, min($this->read_stream_size - $this->read_stream_read, $length)); // Remaining upload data or cURL's requested chunk size
+ $this->read_stream_read += strlen($read);
+ $out = false === $read ? '' : $read;
+ // Execute callback function
+ if ($this->registered_streaming_read_callback) {
+ call_user_func($this->registered_streaming_read_callback, $curl_handle, $file_handle, $out);
+ }
+ return $out;
+ }
+
+ /**
+ * A callback function that is invoked by cURL for streaming down.
+ *
+ * @param resource $curl_handle (Required) The cURL handle for the request.
+ * @param binary $data (Required) The data to write.
+ * @return integer The number of bytes written.
+ */
+ public function streamingWriteCallback($curl_handle, $data)
+ {
+ $length = strlen($data);
+ $written_total = 0;
+ $written_last = 0;
+ while ($written_total < $length) {
+ $written_last = fwrite($this->write_stream, substr($data, $written_total));
+ if (false === $written_last) {
+ return $written_total;
+ }
+ $written_total += $written_last;
+ }
+ // Execute callback function
+ if ($this->registered_streaming_write_callback) {
+ call_user_func($this->registered_streaming_write_callback, $curl_handle, $written_total);
+ }
+ return $written_total;
+ }
+
+ /**
+ * Prepares and adds the details of the cURL request. This can be passed along to a
+ * function.
+ *
+ * @return resource The handle for the cURL object.
+ */
+ public function prepRequest()
+ {
+ $curl_handle = curl_init();
+ // Set default options.
+ curl_setopt($curl_handle, CURLOPT_URL, $this->request_url);
+ curl_setopt($curl_handle, CURLOPT_FILETIME, true);
+ curl_setopt($curl_handle, CURLOPT_FRESH_CONNECT, false);
+ curl_setopt($curl_handle, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($curl_handle, CURLOPT_SSL_VERIFYHOST, true);
+ curl_setopt($curl_handle, CURLOPT_CLOSEPOLICY, CURLCLOSEPOLICY_LEAST_RECENTLY_USED);
+ curl_setopt($curl_handle, CURLOPT_MAXREDIRS, 5);
+ curl_setopt($curl_handle, CURLOPT_HEADER, true);
+ curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($curl_handle, CURLOPT_TIMEOUT, 5184000);
+ curl_setopt($curl_handle, CURLOPT_CONNECTTIMEOUT, 120);
+ curl_setopt($curl_handle, CURLOPT_NOSIGNAL, true);
+ curl_setopt($curl_handle, CURLOPT_REFERER, $this->request_url);
+ curl_setopt($curl_handle, CURLOPT_USERAGENT, $this->useragent);
+ curl_setopt($curl_handle, CURLOPT_READFUNCTION, array(
+ $this,
+ 'streaming_read_callback'));
+ if ($this->debug_mode) {
+ curl_setopt($curl_handle, CURLOPT_VERBOSE, true);
+ }
+ //if (! ini_get ( 'safe_mode' )) {
+ //modify by zhengkan
+ //curl_setopt($curl_handle, CURLOPT_FOLLOWLOCATION, true);
+ //}
+ // Enable a proxy connection if requested.
+ if ($this->proxy) {
+ curl_setopt($curl_handle, CURLOPT_HTTPPROXYTUNNEL, true);
+ $host = $this->proxy['host'];
+ $host .= ($this->proxy['port']) ? ':' . $this->proxy['port'] : '';
+ curl_setopt($curl_handle, CURLOPT_PROXY, $host);
+ if (isset($this->proxy['user']) && isset($this->proxy['pass'])) {
+ curl_setopt($curl_handle, CURLOPT_PROXYUSERPWD, $this->proxy['user'] . ':' . $this->proxy['pass']);
+ }
+ }
+ // Set credentials for HTTP Basic/Digest Authentication.
+ if ($this->username && $this->password) {
+ curl_setopt($curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
+ curl_setopt($curl_handle, CURLOPT_USERPWD, $this->username . ':' . $this->password);
+ }
+ // Handle the encoding if we can.
+ if (extension_loaded('zlib')) {
+ curl_setopt($curl_handle, CURLOPT_ENCODING, '');
+ }
+ // Process custom headers
+ if (isset($this->request_headers) && count($this->request_headers)) {
+ $temp_headers = array();
+ foreach ($this->request_headers as $k => $v) {
+ $temp_headers[] = $k . ': ' . $v;
+ }
+ curl_setopt($curl_handle, CURLOPT_HTTPHEADER, $temp_headers);
+ }
+ switch ($this->method) {
+ case self::HTTP_PUT:
+ curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, 'PUT');
+ if (isset($this->read_stream)) {
+ if (!isset($this->read_stream_size) || $this->read_stream_size < 0) {
+ throw new BCS_RequestCore_Exception('The stream size for the streaming upload cannot be determined.');
+ }
+ curl_setopt($curl_handle, CURLOPT_INFILESIZE, $this->read_stream_size);
+ curl_setopt($curl_handle, CURLOPT_UPLOAD, true);
+ } else {
+ curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $this->request_body);
+ }
+ break;
+ case self::HTTP_POST:
+ curl_setopt($curl_handle, CURLOPT_POST, true);
+ curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $this->request_body);
+ break;
+ case self::HTTP_HEAD:
+ curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, self::HTTP_HEAD);
+ curl_setopt($curl_handle, CURLOPT_NOBODY, 1);
+ break;
+ default: // Assumed GET
+ curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, $this->method);
+ if (isset($this->write_stream)) {
+ curl_setopt($curl_handle, CURLOPT_WRITEFUNCTION, array(
+ $this,
+ 'streaming_write_callback'));
+ curl_setopt($curl_handle, CURLOPT_HEADER, false);
+ } else {
+ curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $this->request_body);
+ }
+ break;
+ }
+ // Merge in the CURLOPTs
+ if (isset($this->curlopts) && sizeof($this->curlopts) > 0) {
+ foreach ($this->curlopts as $k => $v) {
+ curl_setopt($curl_handle, $k, $v);
+ }
+ }
+ return $curl_handle;
+ }
+
+ /**
+ * is the environment BAE?
+ * @return boolean the result of the answer
+ */
+ private function isBaeEnv()
+ {
+ if (isset($_SERVER['HTTP_HOST'])) {
+ $host = $_SERVER['HTTP_HOST'];
+ $pos = strpos($host, '.');
+ if (false !== $pos) {
+ $substr = substr($host, $pos + 1);
+ if ('duapp.com' == $substr) {
+ return true;
+ }
+ }
+ }
+ if (isset($_SERVER["HTTP_BAE_LOGID"])) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Take the post-processed cURL data and break it down into useful header/body/info chunks. Uses the
+ * data stored in the `curl_handle` and `response` properties unless replacement data is passed in via
+ * parameters.
+ *
+ * @param resource $curl_handle (Optional) The reference to the already executed cURL request.
+ * @param string $response (Optional) The actual response content itself that needs to be parsed.
+ * @return BCS_ResponseCore A object containing a parsed HTTP response.
+ */
+ public function processResponse($curl_handle = null, $response = null)
+ {
+ // Accept a custom one if it's passed.
+ if ($curl_handle && $response) {
+ $this->curl_handle = $curl_handle;
+ $this->response = $response;
+ }
+ // As long as this came back as a valid resource...
+ if (is_resource($this->curl_handle)) {
+ // Determine what's what.
+ $header_size = curl_getinfo($this->curl_handle, CURLINFO_HEADER_SIZE);
+ $this->response_headers = substr($this->response, 0, $header_size);
+ $this->response_body = substr($this->response, $header_size);
+ $this->response_code = curl_getinfo($this->curl_handle, CURLINFO_HTTP_CODE);
+ $this->response_info = curl_getinfo($this->curl_handle);
+ // Parse out the headers
+ $this->response_headers = explode("\r\n\r\n", trim($this->response_headers));
+ $this->response_headers = array_pop($this->response_headers);
+ $this->response_headers = explode("\r\n", $this->response_headers);
+ array_shift($this->response_headers);
+ // Loop through and split up the headers.
+ $header_assoc = array();
+ foreach ($this->response_headers as $header) {
+ $kv = explode(': ', $header);
+ //$header_assoc [strtolower ( $kv [0] )] = $kv [1];
+ $header_assoc[$kv[0]] = $kv[1];
+ }
+ // Reset the headers to the appropriate property.
+ $this->response_headers = $header_assoc;
+ $this->response_headers['_info'] = $this->response_info;
+ $this->response_headers['_info']['method'] = $this->method;
+ if ($curl_handle && $response) {
+ $class = '\Think\Upload\Driver\Bcs\\' . $this->response_class;
+ return new $class($this->response_headers, $this->response_body, $this->response_code, $this->curl_handle);
+ }
+ }
+ // Return false
+ return false;
+ }
+
+ /**
+ * Sends the request, calling necessary utility functions to update built-in properties.
+ *
+ * @param boolean $parse (Optional) Whether to parse the response with BCS_ResponseCore or not.
+ * @return string The resulting unparsed data from the request.
+ */
+ public function sendRequest($parse = false)
+ {
+ if (false === $this->isBaeEnv()) {
+ set_time_limit(0);
+ }
+ $curl_handle = $this->prepRequest();
+ $this->response = curl_exec($curl_handle);
+ if (false === $this->response ||
+ (self::HTTP_GET === $this->method &&
+ curl_errno($curl_handle) === CURLE_PARTIAL_FILE)) {
+ throw new BCS_RequestCore_Exception('cURL resource: ' . (string) $curl_handle . '; cURL error: ' . curl_error($curl_handle) . ' (' . curl_errno($curl_handle) . ')');
+ }
+ $parsed_response = $this->processResponse($curl_handle, $this->response);
+ curl_close($curl_handle);
+ if ($parse) {
+ return $parsed_response;
+ }
+ return $this->response;
+ }
+
+ /**
+ * Sends the request using , enabling parallel requests. Uses the "rolling" method.
+ *
+ * @param array $handles (Required) An indexed array of cURL handles to process simultaneously.
+ * @param array $opt (Optional) An associative array of parameters that can have the following keys:
+ * callback
- string|array
- Optional - The string name of a function to pass the response data to. If this is a method, pass an array where the [0]
index is the class and the [1]
index is the method name.
+ * limit
- integer
- Optional - The number of simultaneous requests to make. This can be useful for scaling around slow server responses. Defaults to trusting cURLs judgement as to how many to use.
+ * @return array Post-processed cURL responses.
+ */
+ public function sendMultiRequest($handles, $opt = null)
+ {
+ if (false === $this->isBaeEnv()) {
+ set_time_limit(0);
+ }
+ // Skip everything if there are no handles to process.
+ if (count($handles) === 0) {
+ return array();
+ }
+
+ if (!$opt) {
+ $opt = array();
+ }
+
+ // Initialize any missing options
+ $limit = isset($opt['limit']) ? $opt['limit'] : -1;
+ // Initialize
+ $handle_list = $handles;
+ $http = new $this->request_class();
+ $multi_handle = curl_multi_init();
+ $handles_post = array();
+ $added = count($handles);
+ $last_handle = null;
+ $count = 0;
+ $i = 0;
+ // Loop through the cURL handles and add as many as it set by the limit parameter.
+ while ($i < $added) {
+ if ($limit > 0 && $i >= $limit) {
+ break;
+ }
+
+ curl_multi_add_handle($multi_handle, array_shift($handles));
+ $i++;
+ }
+ do {
+ $active = false;
+ // Start executing and wait for a response.
+ while (($status = curl_multi_exec($multi_handle, $active)) === CURLM_CALL_MULTI_PERFORM) {
+ // Start looking for possible responses immediately when we have to add more handles
+ if (count($handles) > 0) {
+ break;
+ }
+
+ }
+ // Figure out which requests finished.
+ $to_process = array();
+ while ($done = curl_multi_info_read($multi_handle)) {
+ // Since curl_errno() isn't reliable for handles that were in multirequests, we check the 'result' of the info read, which contains the curl error number, (listed here http://curl.haxx.se/libcurl/c/libcurl-errors.html )
+ if ($done['result'] > 0) {
+ throw new BCS_RequestCore_Exception('cURL resource: ' . (string) $done['handle'] . '; cURL error: ' . curl_error($done['handle']) . ' (' . $done['result'] . ')');
+ } // Because curl_multi_info_read() might return more than one message about a request, we check to see if this request is already in our array of completed requests
+ elseif (!isset($to_process[(int) $done['handle']])) {
+ $to_process[(int) $done['handle']] = $done;
+ }
+ }
+ // Actually deal with the request
+ foreach ($to_process as $pkey => $done) {
+ $response = $http->processResponse($done['handle'], curl_multi_getcontent($done['handle']));
+ $key = array_search($done['handle'], $handle_list, true);
+ $handles_post[$key] = $response;
+ if (count($handles) > 0) {
+ curl_multi_add_handle($multi_handle, array_shift($handles));
+ }
+ curl_multi_remove_handle($multi_handle, $done['handle']);
+ curl_close($done['handle']);
+ }
+ } while ($active || count($handles_post) < $added);
+ curl_multi_close($multi_handle);
+ ksort($handles_post, SORT_NUMERIC);
+ return $handles_post;
+ }
+
+ /*%******************************************************************************************%*/
+ // RESPONSE METHODS
+ /**
+ * Get the HTTP response headers from the request.
+ *
+ * @param string $header (Optional) A specific header value to return. Defaults to all headers.
+ * @return string|array All or selected header values.
+ */
+ public function getResponseHeader($header = null)
+ {
+ if ($header) {
+ // return $this->response_headers [strtolower ( $header )];
+ return $this->response_headers[$header];
+ }
+ return $this->response_headers;
+ }
+
+ /**
+ * Get the HTTP response body from the request.
+ *
+ * @return string The response body.
+ */
+ public function getResponseBody()
+ {
+ return $this->response_body;
+ }
+
+ /**
+ * Get the HTTP response code from the request.
+ *
+ * @return string The HTTP response code.
+ */
+ public function getResponseCode()
+ {
+ return $this->response_code;
+ }
+}
+/**
+ * Container for all response-related methods.
+ */
+class BcsResponsecore
+{
+ /**
+ * Stores the HTTP header information.
+ */
+ public $header;
+ /**
+ * Stores the SimpleXML response.
+ */
+ public $body;
+ /**
+ * Stores the HTTP response code.
+ */
+ public $status;
+
+ /**
+ * Constructs a new instance of this class.
+ *
+ * @param array $header (Required) Associative array of HTTP headers (typically returned by ).
+ * @param string $body (Required) XML-formatted response from AWS.
+ * @param integer $status (Optional) HTTP response status code from the request.
+ * @return object Contains an `header` property (HTTP headers as an associative array), a or `body` property, and an `status` code.
+ */
+ public function __construct($header, $body, $status = null)
+ {
+ $this->header = $header;
+ $this->body = $body;
+ $this->status = $status;
+ return $this;
+ }
+
+ /**
+ * Did we receive the status code we expected?
+ *
+ * @param integer|array $codes (Optional) The status code(s) to expect. Pass an for a single acceptable value, or an of integers for multiple acceptable values.
+ * @return boolean Whether we received the expected status code or not.
+ */
+ public function isOK($codes = array(200, 201, 204, 206))
+ {
+ if (is_array($codes)) {
+ return in_array($this->status, $codes);
+ }
+ return $this->status === $codes;
+ }
+}
+/**
+ * Default BCS_RequestCore Exception.
+ */
+class BcsRequestcoreException extends \Exception
+{
+}
diff --git a/Framework/Library/Think/Upload/Driver/Ftp.class.php b/Framework/Library/Think/Upload/Driver/Ftp.class.php
new file mode 100644
index 00000000..967e6928
--- /dev/null
+++ b/Framework/Library/Think/Upload/Driver/Ftp.class.php
@@ -0,0 +1,173 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think\Upload\Driver;
+
+class Ftp
+{
+ /**
+ * 上传文件根目录
+ * @var string
+ */
+ private $rootPath;
+
+ /**
+ * 本地上传错误信息
+ * @var string
+ */
+ private $error = ''; //上传错误信息
+
+ /**
+ * FTP连接
+ * @var resource
+ */
+ private $link;
+
+ private $config = array(
+ 'host' => '', //服务器
+ 'port' => 21, //端口
+ 'timeout' => 90, //超时时间
+ 'username' => '', //用户名
+ 'password' => '', //密码
+ );
+
+ /**
+ * 构造函数,用于设置上传根路径
+ * @param array $config FTP配置
+ */
+ public function __construct($config)
+ {
+ /* 默认FTP配置 */
+ $this->config = array_merge($this->config, $config);
+
+ /* 登录FTP服务器 */
+ if (!$this->login()) {
+ E($this->error);
+ }
+ }
+
+ /**
+ * 检测上传根目录
+ * @param string $rootpath 根目录
+ * @return boolean true-检测通过,false-检测失败
+ */
+ public function checkRootPath($rootpath)
+ {
+ /* 设置根目录 */
+ $this->rootPath = ftp_pwd($this->link) . '/' . ltrim($rootpath, '/');
+
+ if (!@ftp_chdir($this->link, $this->rootPath)) {
+ $this->error = '上传根目录不存在!';
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 检测上传目录
+ * @param string $savepath 上传目录
+ * @return boolean 检测结果,true-通过,false-失败
+ */
+ public function checkSavePath($savepath)
+ {
+ /* 检测并创建目录 */
+ if (!$this->mkdir($savepath)) {
+ return false;
+ } else {
+ //TODO:检测目录是否可写
+ return true;
+ }
+ }
+
+ /**
+ * 保存指定文件
+ * @param array $file 保存的文件信息
+ * @param boolean $replace 同名文件是否覆盖
+ * @return boolean 保存状态,true-成功,false-失败
+ */
+ public function save($file, $replace = true)
+ {
+ $filename = $this->rootPath . $file['savepath'] . $file['savename'];
+
+ /* 不覆盖同名文件 */
+ // if (!$replace && is_file($filename)) {
+ // $this->error = '存在同名文件' . $file['savename'];
+ // return false;
+ // }
+
+ /* 移动文件 */
+ if (!ftp_put($this->link, $filename, $file['tmp_name'], FTP_BINARY)) {
+ $this->error = '文件上传保存错误!';
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 创建目录
+ * @param string $savepath 要创建的目录
+ * @return boolean 创建状态,true-成功,false-失败
+ */
+ public function mkdir($savepath)
+ {
+ $dir = $this->rootPath . $savepath;
+ if (ftp_chdir($this->link, $dir)) {
+ return true;
+ }
+
+ if (ftp_mkdir($this->link, $dir)) {
+ return true;
+ } elseif ($this->mkdir(dirname($savepath)) && ftp_mkdir($this->link, $dir)) {
+ return true;
+ } else {
+ $this->error = "目录 {$savepath} 创建失败!";
+ return false;
+ }
+ }
+
+ /**
+ * 获取最后一次上传错误信息
+ * @return string 错误信息
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * 登录到FTP服务器
+ * @return boolean true-登录成功,false-登录失败
+ */
+ private function login()
+ {
+ extract($this->config);
+ $this->link = ftp_connect($host, $port, $timeout);
+ if ($this->link) {
+ if (ftp_login($this->link, $username, $password)) {
+ return true;
+ } else {
+ $this->error = "无法登录到FTP服务器:username - {$username}";
+ }
+ } else {
+ $this->error = "无法连接到FTP服务器:{$host}";
+ }
+ return false;
+ }
+
+ /**
+ * 析构方法,用于断开当前FTP连接
+ */
+ public function __destruct()
+ {
+ ftp_close($this->link);
+ }
+
+}
diff --git a/Framework/Library/Think/Upload/Driver/Local.class.php b/Framework/Library/Think/Upload/Driver/Local.class.php
new file mode 100644
index 00000000..6e78ce77
--- /dev/null
+++ b/Framework/Library/Think/Upload/Driver/Local.class.php
@@ -0,0 +1,126 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think\Upload\Driver;
+
+class Local
+{
+ /**
+ * 上传文件根目录
+ * @var string
+ */
+ private $rootPath;
+
+ /**
+ * 本地上传错误信息
+ * @var string
+ */
+ private $error = ''; //上传错误信息
+
+ /**
+ * 构造函数,用于设置上传根路径
+ */
+ public function __construct($config = null)
+ {
+
+ }
+
+ /**
+ * 检测上传根目录
+ * @param string $rootpath 根目录
+ * @return boolean true-检测通过,false-检测失败
+ */
+ public function checkRootPath($rootpath)
+ {
+ if (!(is_dir($rootpath) && is_writable($rootpath))) {
+ $this->error = '上传根目录不存在!请尝试手动创建:' . $rootpath;
+ return false;
+ }
+ $this->rootPath = $rootpath;
+ return true;
+ }
+
+ /**
+ * 检测上传目录
+ * @param string $savepath 上传目录
+ * @return boolean 检测结果,true-通过,false-失败
+ */
+ public function checkSavePath($savepath)
+ {
+ /* 检测并创建目录 */
+ if (!$this->mkdir($savepath)) {
+ return false;
+ } else {
+ /* 检测目录是否可写 */
+ if (!is_writable($this->rootPath . $savepath)) {
+ $this->error = '上传目录 ' . $savepath . ' 不可写!';
+ return false;
+ } else {
+ return true;
+ }
+ }
+ }
+
+ /**
+ * 保存指定文件
+ * @param array $file 保存的文件信息
+ * @param boolean $replace 同名文件是否覆盖
+ * @return boolean 保存状态,true-成功,false-失败
+ */
+ public function save($file, $replace = true)
+ {
+ $filename = $this->rootPath . $file['savepath'] . $file['savename'];
+
+ /* 不覆盖同名文件 */
+ if (!$replace && is_file($filename)) {
+ $this->error = '存在同名文件' . $file['savename'];
+ return false;
+ }
+
+ /* 移动文件 */
+ if (!move_uploaded_file($file['tmp_name'], $filename)) {
+ $this->error = '文件上传保存错误!';
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * 创建目录
+ * @param string $savepath 要创建的目录
+ * @return boolean 创建状态,true-成功,false-失败
+ */
+ public function mkdir($savepath)
+ {
+ $dir = $this->rootPath . $savepath;
+ if (is_dir($dir)) {
+ return true;
+ }
+
+ if (mkdir($dir, 0777, true)) {
+ return true;
+ } else {
+ $this->error = "目录 {$savepath} 创建失败!";
+ return false;
+ }
+ }
+
+ /**
+ * 获取最后一次上传错误信息
+ * @return string 错误信息
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+}
diff --git a/Framework/Library/Think/Upload/Driver/Qiniu.class.php b/Framework/Library/Think/Upload/Driver/Qiniu.class.php
new file mode 100644
index 00000000..48f6c2e1
--- /dev/null
+++ b/Framework/Library/Think/Upload/Driver/Qiniu.class.php
@@ -0,0 +1,110 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think\Upload\Driver;
+
+use Think\Upload\Driver\Qiniu\QiniuStorage;
+
+class Qiniu
+{
+ /**
+ * 上传文件根目录
+ * @var string
+ */
+ private $rootPath;
+
+ /**
+ * 上传错误信息
+ * @var string
+ */
+ private $error = '';
+
+ private $config = array(
+ 'secretKey' => '', //七牛服务器
+ 'accessKey' => '', //七牛用户
+ 'domain' => '', //七牛密码
+ 'bucket' => '', //空间名称
+ 'timeout' => 300, //超时时间
+ );
+
+ /**
+ * 构造函数,用于设置上传根路径
+ * @param array $config FTP配置
+ */
+ public function __construct($config)
+ {
+ $this->config = array_merge($this->config, $config);
+ /* 设置根目录 */
+ $this->qiniu = new QiniuStorage($config);
+ }
+
+ /**
+ * 检测上传根目录(七牛上传时支持自动创建目录,直接返回)
+ * @param string $rootpath 根目录
+ * @return boolean true-检测通过,false-检测失败
+ */
+ public function checkRootPath($rootpath)
+ {
+ $this->rootPath = trim($rootpath, './') . '/';
+ return true;
+ }
+
+ /**
+ * 检测上传目录(七牛上传时支持自动创建目录,直接返回)
+ * @param string $savepath 上传目录
+ * @return boolean 检测结果,true-通过,false-失败
+ */
+ public function checkSavePath($savepath)
+ {
+ return true;
+ }
+
+ /**
+ * 创建文件夹 (七牛上传时支持自动创建目录,直接返回)
+ * @param string $savepath 目录名称
+ * @return boolean true-创建成功,false-创建失败
+ */
+ public function mkdir($savepath)
+ {
+ return true;
+ }
+
+ /**
+ * 保存指定文件
+ * @param array $file 保存的文件信息
+ * @param boolean $replace 同名文件是否覆盖
+ * @return boolean 保存状态,true-成功,false-失败
+ */
+ public function save(&$file, $replace = true)
+ {
+ $file['name'] = $file['savepath'] . $file['savename'];
+ $key = str_replace('/', '_', $file['name']);
+ $upfile = array(
+ 'name' => 'file',
+ 'fileName' => $key,
+ 'fileBody' => file_get_contents($file['tmp_name']),
+ );
+ $config = array();
+ $result = $this->qiniu->upload($config, $upfile);
+ $url = $this->qiniu->downlink($key);
+ $file['url'] = $url;
+ return false === $result ? false : true;
+ }
+
+ /**
+ * 获取最后一次上传错误信息
+ * @return string 错误信息
+ */
+ public function getError()
+ {
+ return $this->qiniu->errorStr;
+ }
+}
diff --git a/Framework/Library/Think/Upload/Driver/Qiniu/QiniuStorage.class.php b/Framework/Library/Think/Upload/Driver/Qiniu/QiniuStorage.class.php
new file mode 100644
index 00000000..e6756c15
--- /dev/null
+++ b/Framework/Library/Think/Upload/Driver/Qiniu/QiniuStorage.class.php
@@ -0,0 +1,366 @@
+sk = $config['secretKey'];
+ $this->ak = $config['accessKey'];
+ $this->domain = $config['domain'];
+ $this->bucket = $config['bucket'];
+ $this->timeout = isset($config['timeout']) ? $config['timeout'] : 3600;
+ }
+
+ public static function sign($sk, $ak, $data)
+ {
+ $sign = hash_hmac('sha1', $data, $sk, true);
+ return $ak . ':' . self::qiniuEncode($sign);
+ }
+
+ public static function signWithData($sk, $ak, $data)
+ {
+ $data = self::qiniuEncode($data);
+ return self::sign($sk, $ak, $data) . ':' . $data;
+ }
+
+ public function accessToken($url, $body = '')
+ {
+ $parsed_url = parse_url($url);
+ $path = $parsed_url['path'];
+ $access = $path;
+ if (isset($parsed_url['query'])) {
+ $access .= "?" . $parsed_url['query'];
+ }
+ $access .= "\n";
+
+ if ($body) {
+ $access .= $body;
+ }
+ return self::sign($this->sk, $this->ak, $access);
+ }
+
+ public function UploadToken($sk, $ak, $param)
+ {
+ $param['deadline'] = 0 == $param['Expires'] ? 3600 : $param['Expires'];
+ $param['deadline'] += time();
+ $data = array('scope' => $this->bucket, 'deadline' => $param['deadline']);
+ if (!empty($param['CallbackUrl'])) {
+ $data['callbackUrl'] = $param['CallbackUrl'];
+ }
+ if (!empty($param['CallbackBody'])) {
+ $data['callbackBody'] = $param['CallbackBody'];
+ }
+ if (!empty($param['ReturnUrl'])) {
+ $data['returnUrl'] = $param['ReturnUrl'];
+ }
+ if (!empty($param['ReturnBody'])) {
+ $data['returnBody'] = $param['ReturnBody'];
+ }
+ if (!empty($param['AsyncOps'])) {
+ $data['asyncOps'] = $param['AsyncOps'];
+ }
+ if (!empty($param['EndUser'])) {
+ $data['endUser'] = $param['EndUser'];
+ }
+ $data = json_encode($data);
+ return self::SignWithData($sk, $ak, $data);
+ }
+
+ public function upload($config, $file)
+ {
+ $uploadToken = $this->UploadToken($this->sk, $this->ak, $config);
+
+ $url = "{$this->QINIU_UP_HOST}";
+ $mimeBoundary = md5(microtime());
+ $header = array('Content-Type' => 'multipart/form-data;boundary=' . $mimeBoundary);
+ $data = array();
+
+ $fields = array(
+ 'token' => $uploadToken,
+ 'key' => $config['saveName'] ?: $file['fileName'],
+ );
+
+ if (is_array($config['custom_fields']) && array() !== $config['custom_fields']) {
+ $fields = array_merge($fields, $config['custom_fields']);
+ }
+
+ foreach ($fields as $name => $val) {
+ array_push($data, '--' . $mimeBoundary);
+ array_push($data, "Content-Disposition: form-data; name=\"$name\"");
+ array_push($data, '');
+ array_push($data, $val);
+ }
+
+ //文件
+ array_push($data, '--' . $mimeBoundary);
+ $name = $file['name'];
+ $fileName = $file['fileName'];
+ $fileBody = $file['fileBody'];
+ $fileName = self::qiniuEscapequotes($fileName);
+ array_push($data, "Content-Disposition: form-data; name=\"$name\"; filename=\"$fileName\"");
+ array_push($data, 'Content-Type: application/octet-stream');
+ array_push($data, '');
+ array_push($data, $fileBody);
+
+ array_push($data, '--' . $mimeBoundary . '--');
+ array_push($data, '');
+
+ $body = implode("\r\n", $data);
+ $response = $this->request($url, 'POST', $header, $body);
+ return $response;
+ }
+
+ public function dealWithType($key, $type)
+ {
+ $param = $this->buildUrlParam();
+ $url = '';
+
+ switch ($type) {
+ case 'img':
+ $url = $this->downLink($key);
+ if ($param['imageInfo']) {
+ $url .= '?imageInfo';
+ } else if ($param['exif']) {
+ $url .= '?exif';
+ } else if ($param['imageView']) {
+ $url .= '?imageView/' . $param['mode'];
+ if ($param['w']) {
+ $url .= "/w/{$param['w']}";
+ }
+
+ if ($param['h']) {
+ $url .= "/h/{$param['h']}";
+ }
+
+ if ($param['q']) {
+ $url .= "/q/{$param['q']}";
+ }
+
+ if ($param['format']) {
+ $url .= "/format/{$param['format']}";
+ }
+
+ }
+ break;
+ case 'video': //TODO 视频处理
+ case 'doc':
+ $url = $this->downLink($key);
+ $url .= '?md2html';
+ if (isset($param['mode'])) {
+ $url .= '/' . (int) $param['mode'];
+ }
+
+ if ($param['cssurl']) {
+ $url .= '/' . self::qiniuEncode($param['cssurl']);
+ }
+
+ break;
+
+ }
+ return $url;
+ }
+
+ public function buildUrlParam()
+ {
+ return $_REQUEST;
+ }
+
+ //获取某个路径下的文件列表
+ public function getList($query = array(), $path = '')
+ {
+ $query = array_merge(array('bucket' => $this->bucket), $query);
+ $url = "{$this->QINIU_RSF_HOST}/list?" . http_build_query($query);
+ $accessToken = $this->accessToken($url);
+ $response = $this->request($url, 'POST', array('Authorization' => "QBox $accessToken"));
+ return $response;
+ }
+
+ //获取某个文件的信息
+ public function info($key)
+ {
+ $key = trim($key);
+ $url = "{$this->QINIU_RS_HOST}/stat/" . self::qiniuEncode("{$this->bucket}:{$key}");
+ $accessToken = $this->accessToken($url);
+ $response = $this->request($url, 'POST', array(
+ 'Authorization' => "QBox $accessToken",
+ ));
+ return $response;
+ }
+
+ //获取文件下载资源链接
+ public function downLink($key)
+ {
+ $key = urlencode($key);
+ $key = self::qiniuEscapequotes($key);
+ $url = "http://{$this->domain}/{$key}";
+ return $url;
+ }
+
+ //重命名单个文件
+ public function rename($file, $new_file)
+ {
+ $key = trim($file);
+ $url = "{$this->QINIU_RS_HOST}/move/" . self::qiniuEncode("{$this->bucket}:{$key}") . '/' . self::qiniuEncode("{$this->bucket}:{$new_file}");
+ trace($url);
+ $accessToken = $this->accessToken($url);
+ $response = $this->request($url, 'POST', array('Authorization' => "QBox $accessToken"));
+ return $response;
+ }
+
+ //删除单个文件
+ public function del($file)
+ {
+ $key = trim($file);
+ $url = "{$this->QINIU_RS_HOST}/delete/" . self::qiniuEncode("{$this->bucket}:{$key}");
+ $accessToken = $this->accessToken($url);
+ $response = $this->request($url, 'POST', array('Authorization' => "QBox $accessToken"));
+ return $response;
+ }
+
+ //批量删除文件
+ public function delBatch($files)
+ {
+ $url = $this->QINIU_RS_HOST . '/batch';
+ $ops = array();
+ foreach ($files as $file) {
+ $ops[] = "/delete/" . self::qiniuEncode("{$this->bucket}:{$file}");
+ }
+ $params = 'op=' . implode('&op=', $ops);
+ $url .= '?' . $params;
+ trace($url);
+ $accessToken = $this->accessToken($url);
+ $response = $this->request($url, 'POST', array('Authorization' => "QBox $accessToken"));
+ return $response;
+ }
+
+ public static function qiniuEncode($str)
+ {
+// URLSafeBase64Encode
+ $find = array('+', '/');
+ $replace = array('-', '_');
+ return str_replace($find, $replace, base64_encode($str));
+ }
+
+ public static function qiniuEscapequotes($str)
+ {
+ $find = array("\\", "\"");
+ $replace = array("\\\\", "\\\"");
+ return str_replace($find, $replace, $str);
+ }
+
+ /**
+ * 请求云服务器
+ * @param string $path 请求的PATH
+ * @param string $method 请求方法
+ * @param array $headers 请求header
+ * @param resource $body 上传文件资源
+ * @return boolean
+ */
+ private function request($path, $method, $headers = null, $body = null)
+ {
+ $ch = curl_init($path);
+
+ $_headers = array('Expect:');
+ if (!is_null($headers) && is_array($headers)) {
+ foreach ($headers as $k => $v) {
+ array_push($_headers, "{$k}: {$v}");
+ }
+ }
+
+ $length = 0;
+ $date = gmdate('D, d M Y H:i:s \G\M\T');
+
+ if (!is_null($body)) {
+ if (is_resource($body)) {
+ fseek($body, 0, SEEK_END);
+ $length = ftell($body);
+ fseek($body, 0);
+
+ array_push($_headers, "Content-Length: {$length}");
+ curl_setopt($ch, CURLOPT_INFILE, $body);
+ curl_setopt($ch, CURLOPT_INFILESIZE, $length);
+ } else {
+ $length = @strlen($body);
+ array_push($_headers, "Content-Length: {$length}");
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
+ }
+ } else {
+ array_push($_headers, "Content-Length: {$length}");
+ }
+
+ // array_push($_headers, 'Authorization: ' . $this->sign($method, $uri, $date, $length));
+ array_push($_headers, "Date: {$date}");
+
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $_headers);
+ curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
+ curl_setopt($ch, CURLOPT_HEADER, 1);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
+ curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
+
+ if ('PUT' == $method || 'POST' == $method) {
+ curl_setopt($ch, CURLOPT_POST, 1);
+ } else {
+ curl_setopt($ch, CURLOPT_POST, 0);
+ }
+
+ if ('HEAD' == $method) {
+ curl_setopt($ch, CURLOPT_NOBODY, true);
+ }
+
+ $response = curl_exec($ch);
+ $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ curl_close($ch);
+ list($header, $body) = explode("\r\n\r\n", $response, 2);
+ if (200 == $status) {
+ if ('GET' == $method) {
+ return $body;
+ } else {
+ return $this->response($response);
+ }
+ } else {
+ $this->error($header, $body);
+ return false;
+ }
+ }
+
+ /**
+ * 获取响应数据
+ * @param string $text 响应头字符串
+ * @return array 响应数据列表
+ */
+ private function response($text)
+ {
+ $headers = explode(PHP_EOL, $text);
+ $items = array();
+ foreach ($headers as $header) {
+ $header = trim($header);
+ if (strpos($header, '{') !== false) {
+ $items = json_decode($header, 1);
+ break;
+ }
+ }
+ return $items;
+ }
+
+ /**
+ * 获取请求错误信息
+ * @param string $header 请求返回头信息
+ */
+ private function error($header, $body)
+ {
+ list($status, $stash) = explode("\r\n", $header, 2);
+ list($v, $code, $message) = explode(" ", $status, 3);
+ $message = is_null($message) ? 'File Not Found' : "[{$status}]:{$message}]";
+ $this->error = $message;
+ $this->errorStr = json_decode($body, 1);
+ $this->errorStr = $this->errorStr['error'];
+ }
+}
diff --git a/Framework/Library/Think/Upload/Driver/Sae.class.php b/Framework/Library/Think/Upload/Driver/Sae.class.php
new file mode 100644
index 00000000..efa4df03
--- /dev/null
+++ b/Framework/Library/Think/Upload/Driver/Sae.class.php
@@ -0,0 +1,114 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think\Upload\Driver;
+
+class Sae
+{
+ /**
+ * Storage的Domain
+ * @var string
+ */
+ private $domain = '';
+
+ private $rootPath = '';
+
+ /**
+ * 本地上传错误信息
+ * @var string
+ */
+ private $error = '';
+
+ /**
+ * 构造函数,设置storage的domain, 如果有传配置,则domain为配置项,如果没有传domain为第一个路径的目录名称。
+ * @param mixed $config 上传配置
+ */
+ public function __construct($config = null)
+ {
+ if (is_array($config) && !empty($config['domain'])) {
+ $this->domain = strtolower($config['domain']);
+ }
+ }
+
+ /**
+ * 检测上传根目录
+ * @param string $rootpath 根目录
+ * @return boolean true-检测通过,false-检测失败
+ */
+ public function checkRootPath($rootpath)
+ {
+ $rootpath = trim($rootpath, './');
+ if (!$this->domain) {
+ $rootpath = explode('/', $rootpath);
+ $this->domain = strtolower(array_shift($rootpath));
+ $rootpath = implode('/', $rootpath);
+ }
+
+ $this->rootPath = $rootpath;
+ $st = new \SaeStorage();
+ if (false === $st->getDomainCapacity($this->domain)) {
+ $this->error = '您好像没有建立Storage的domain[' . $this->domain . ']';
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 检测上传目录
+ * @param string $savepath 上传目录
+ * @return boolean 检测结果,true-通过,false-失败
+ */
+ public function checkSavePath($savepath)
+ {
+ return true;
+ }
+
+ /**
+ * 保存指定文件
+ * @param array $file 保存的文件信息
+ * @param boolean $replace 同名文件是否覆盖
+ * @return boolean 保存状态,true-成功,false-失败
+ */
+ public function save(&$file, $replace = true)
+ {
+ $filename = ltrim($this->rootPath . '/' . $file['savepath'] . $file['savename'], '/');
+ $st = new \SaeStorage();
+ /* 不覆盖同名文件 */
+ if (!$replace && $st->fileExists($this->domain, $filename)) {
+ $this->error = '存在同名文件' . $file['savename'];
+ return false;
+ }
+
+ /* 移动文件 */
+ if (!$st->upload($this->domain, $filename, $file['tmp_name'])) {
+ $this->error = '文件上传保存错误![' . $st->errno() . ']:' . $st->errmsg();
+ return false;
+ } else {
+ $file['url'] = $st->getUrl($this->domain, $filename);
+ }
+ return true;
+ }
+
+ public function mkdir()
+ {
+ return true;
+ }
+
+ /**
+ * 获取最后一次上传错误信息
+ * @return string 错误信息
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+}
diff --git a/Framework/Library/Think/Upload/Driver/Tietuku.class.php b/Framework/Library/Think/Upload/Driver/Tietuku.class.php
new file mode 100644
index 00000000..f31615f3
--- /dev/null
+++ b/Framework/Library/Think/Upload/Driver/Tietuku.class.php
@@ -0,0 +1,100 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think\Upload\Driver;
+
+class Tietuku {
+
+ /**
+ * 上传文件根目录
+ * @var string
+ */
+ private $rootPath;
+
+ /**
+ * 上传错误信息
+ * @var string
+ */
+ private $error = '';
+ private $config = array(
+ 'secretKey' => '', //贴图库sk
+ 'accessKey' => '', //贴图库ak
+ 'aid' => '', //贴图库相册id
+ );
+
+ /**
+ * 构造函数,用于设置上传根路径
+ * @param array $config FTP配置
+ */
+ public function __construct($config) {
+ $this->config = array_merge($this->config, $config);
+ require_once __DIR__.DIRECTORY_SEPARATOR.'Tietuku'.DIRECTORY_SEPARATOR.'tietuku.class.php';
+ /* 设置根目录 */
+ $this->Client = new \TTKClient($this->config['accessKey'], $this->config['secretKey']);
+ }
+
+ /**
+ * 检测上传根目录(贴图库上传时支持自动创建目录,直接返回)
+ * @param string $rootpath 根目录
+ * @return boolean true-检测通过,false-检测失败
+ */
+ public function checkRootPath($rootpath) {
+ $this->rootPath = trim($rootpath, './') . '/';
+ return true;
+ }
+
+ /**
+ * 检测上传目录(贴图库上传时支持自动创建目录,直接返回)
+ * @param string $savepath 上传目录
+ * @return boolean 检测结果,true-通过,false-失败
+ */
+ public function checkSavePath($savepath) {
+ return true;
+ }
+
+ /**
+ * 创建文件夹 (贴图库上传时支持自动创建目录,直接返回)
+ * @param string $savepath 目录名称
+ * @return boolean true-创建成功,false-创建失败
+ */
+ public function mkdir($savepath) {
+ return true;
+ }
+
+ /**
+ * 保存指定文件
+ * @param array $file 保存的文件信息
+ * @param boolean $replace 同名文件是否覆盖
+ * @return boolean 保存状态,true-成功,false-失败
+ */
+ public function save(&$file, $replace = true) {
+ $file['name'] = $file['savepath'] . $file['savename'];
+ $result = $this->Client->uploadFile($this->config['aid'],$file['tmp_name'], $file['name']);
+ $result = json_decode($result, true);
+ d_f('upload', $result);
+ if(isset($result[0]))
+ $result = $result[0];
+ if(isset($result['code']))
+ $this->error = 'token错误';
+ else
+ $file['url'] = $result['linkurl'];
+ return isset($result['width'])? true: false;
+ }
+
+ /**
+ * 获取最后一次上传错误信息
+ * @return string 错误信息
+ */
+ public function getError() {
+ return $this->error;
+ }
+}
diff --git a/Framework/Library/Think/Upload/Driver/Tietuku/tietuku.class.php b/Framework/Library/Think/Upload/Driver/Tietuku/tietuku.class.php
new file mode 100644
index 00000000..0b5524ee
--- /dev/null
+++ b/Framework/Library/Think/Upload/Driver/Tietuku/tietuku.class.php
@@ -0,0 +1,540 @@
+, qakcn
+ */
+
+/**
+ * 贴图库 Token 生成类
+ *
+ * 生成机制说明请大家参考贴图库开放平台文档:{@link http://open.tietuku.com/doc#safe-token}
+ *
+ * @package TieTuKu
+ * @author Tears, qakcn
+ * @version 1.0
+ */
+class TieTuKuToken{
+ /**
+ * @ignore
+ */
+ public $accesskey;
+ /**
+ * @ignore
+ */
+ public $secretkey;
+ /**
+ * @ignore
+ */
+ private $base64param;
+ /**
+ * 构造函数
+ *
+ * @access public
+ * @param mixed $accesskey 贴图库平台accesskey
+ * @param mixed $secretkey 贴图库平台secretkey
+ * @return void
+ */
+ function __construct($accesskey,$secretkey){
+ if($accesskey == ''||$secretkey =='')
+ return false;
+ $this->accesskey = $accesskey;
+ $this->secretkey = $secretkey;
+ }
+ /**
+ * 将参数进行JSON格式化并且进行url安全的base64编码
+ *
+ * @param array $param 接口所需要的参数
+ * @return mixed 返回该类 可进行连续操作
+ */
+ function dealParam($param){
+ $this->base64param = $this->URLSafeBase64Encode(json_encode($param));
+ return $this;
+ }
+ /**
+ * 生成Token方法
+ * 需要先调用dealParam方法否则返回false
+ *
+ * @return string 成功生成的Token 失败返回false
+ */
+ function createToken(){
+ if(empty($this->base64param)) return false;
+ $sign = $this->signEncode($this->base64param,$this->secretkey);
+ return $this->accesskey.':'.$this->URLSafeBase64Encode($sign).':'.$this->base64param;
+ }
+ /**
+ * Token hash加密方法
+ *
+ * @param string $str 需要进行hash加密的字符串
+ * @param string $key secretkey
+ * @return string hash_hmac sha1 加密后的字符串
+ */
+ function signEncode($str, $key){
+ $hmac_sha1_str = "";
+ if (function_exists('hash_hmac')){
+ $hmac_sha1_str = hash_hmac("sha1", $str, $key, true);
+ } else {
+ $blocksize = 64;
+ $hashfunc = 'sha1';
+ if (strlen($key) > $blocksize){
+ $key = pack('H*', $hashfunc($key));
+ }
+ $key = str_pad($key, $blocksize, chr(0x00));
+ $ipad = str_repeat(chr(0x36), $blocksize);
+ $opad = str_repeat(chr(0x5c), $blocksize);
+ $hmac_sha1_str = pack('H*', $hashfunc(($key ^ $opad) . pack('H*', $hashfunc(($key ^ $ipad) . $str))));
+ }
+ return $hmac_sha1_str;
+ }
+ /**
+ * url安全的base64编码 URLSafeBase64Encode
+ *
+ * @param string $str 需要进行url安全的base64编码的字符串
+ * @return string 返回url安全的base64编码字符串
+ */
+ function URLSafeBase64Encode($str){
+ $find = array('+', '/');
+ $replace = array('-', '_');
+ return str_replace($find, $replace, base64_encode($str));
+ }
+}
+/**
+ * 贴图库 客户端操作类
+ *
+ *
+ * @package TieTuKu
+ * @author Tears
+ * @version 1.0
+ */
+class TTKClient{
+
+ /**
+ * Set up the API root URL.
+ *
+ * @ignore
+ */
+ public $upload_host = "http://up.tietuku.com/";
+ public $host = "http://api.tietuku.com/v1/";
+ /**
+ * Set timeout default.
+ *
+ * @ignore
+ */
+ public $timeout = 60;
+ /**
+ * Set CURL timeout.
+ *
+ * @ignore
+ */
+ public $CURLtimeout = 30;
+ /**
+ * 构造函数
+ *
+ * @access public
+ * @param mixed $accesskey 贴图库平台accesskey
+ * @param mixed $secretkey 贴图库平台secretkey
+ * @return void
+ */
+ function __construct($accesskey,$secretkey){
+ $this->op_Token = new \TieTuKuToken($accesskey,$secretkey);
+ }
+ /**
+ * 查询随机30张推荐的图片
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#list-getrandrec}
+ *
+ * @access public
+ * @param boolean $createToken 是否只返回Token,默认为false。
+ * @return string 如果$createToken=true 返回请求接口的json数据否则只返回Token
+ */
+ function getRandRec($createToken=false){
+ $url = $this->host."/List/";
+ $param['deadline'] = time()+$this->timeout;
+ $param['action'] = 'getrandrec';
+ $Token=$this->op_Token->dealParam($param)->createToken();
+ $data['Token']=$Token;
+ return $createToken?$Token:$this->post($url,$data);
+ }
+ /**
+ * 根据类型ID查询随机30张推荐的图片
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#list-getrandrec}
+ *
+ * @access public
+ * @param int $cid 类型ID。
+ * @param boolean $createToken 是否只返回Token,默认为false。
+ * @return string 如果$createToken=true 返回请求接口的json数据否则只返回Token
+ */
+ function getRandRecByCid($cid,$createToken=false){
+ $url = $this->host."/List/";
+ $param['deadline'] = time()+$this->timeout;
+ $param['action'] = 'getrandrec';
+ $param['cid'] = $cid;
+ $Token=$this->op_Token->dealParam($param)->createToken();
+ $data['Token']=$Token;
+ return $createToken?$Token:$this->post($url,$data);
+ }
+ /**
+ * 根据 图片ID 查询相应的图片详细信息
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#pic-getonepic}
+ *
+ * @access public
+ * @param int $id 图片ID。
+ * @param boolean $createToken 是否只返回Token,默认为false。
+ * @return string 如果$createToken=true 返回请求接口的json数据否则只返回Token
+ */
+ function getOnePicById($id,$createToken=false){
+ $url = $this->host."/Pic/";
+ $param['deadline'] = time()+$this->timeout;
+ $param['action'] = 'getonepic';
+ $param['id'] = $id;
+ $Token=$this->op_Token->dealParam($param)->createToken();
+ $data['Token']=$Token;
+ return $createToken?$Token:$this->post($url,$data);
+ }
+ /**
+ * 根据 图片find_url 查询相应的图片详细信息
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#pic-getonepic}
+ *
+ * @access public
+ * @param string $find_url 图片find_url
+ * @param boolean $createToken 是否只返回Token,默认为false。
+ * @return string 如果$createToken=true 返回请求接口的json数据否则只返回Token
+ */
+ function getOnePicByFind_url($find_url,$createToken=false){
+ $url = $this->host."/Pic/";
+ $param['deadline'] = time()+$this->timeout;
+ $param['action'] = 'getonepic';
+ $param['findurl'] = $find_url;
+ $Token=$this->op_Token->dealParam($param)->createToken();
+ $data['Token']=$Token;
+ return $createToken?$Token:$this->post($url,$data);
+ }
+ /**
+ * 分页查询全部图片列表 每页30张图片
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#list-getnewpic}
+ *
+ * @access public
+ * @param int $page_no 页数,默认为1。
+ * @param boolean $createToken 是否只返回Token,默认为false。
+ * @return string 如果$createToken=true 返回请求接口的json数据否则只返回Token
+ */
+ function getNewPic($page_no=1,$createToken=false){
+ $url = $this->host."/List/";
+ $param['deadline'] = time()+$this->timeout;
+ $param['action'] = 'getnewpic';
+ $param['page_no'] = $page_no;
+ $Token=$this->op_Token->dealParam($param)->createToken();
+ $data['Token']=$Token;
+ return $createToken?$Token:$this->post($url,$data);
+ }
+ /**
+ * 通过类型ID分页查询全部图片列表 每页30张图片
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#list-getnewpic}
+ *
+ * @access public
+ * @param int $cid 类型ID。
+ * @param int $page_no 页数,默认为1。
+ * @param boolean $createToken 是否只返回Token,默认为false。
+ * @return string 如果$createToken=true 返回请求接口的json数据否则只返回Token
+ */
+ function getNewPicByCid($cid,$page_no=1,$createToken=false){
+ $url = $this->host."/List/";
+ $param['deadline'] = time()+$this->timeout;
+ $param['action'] = 'getnewpic';
+ $param['cid'] = $cid;
+ $param['page_no'] = $page_no;
+ $Token=$this->op_Token->dealParam($param)->createToken();
+ $data['Token']=$Token;
+ return $createToken?$Token:$this->post($url,$data);
+ }
+ /**
+ * 根据用户ID查询用户相册列表
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#album-get}
+ *
+ * @access public
+ * @param int $uid 用户ID
+ * @param boolean $createToken 是否只返回Token,默认为false。
+ * @return string 如果$createToken=true 返回请求接口的json数据否则只返回Token
+ */
+ function getAlbumByUid($uid=null,$createToken=false){
+ $url = $this->host."/Album/";
+ $param['deadline'] = time()+$this->timeout;
+ $param['action'] = 'get';
+ if (!empty($uid)) $param['uid'] = $uid;
+ $Token=$this->op_Token->dealParam($param)->createToken();
+ $data['Token']=$Token;
+ return $createToken?$Token:$this->post($url,$data);
+ }
+ /**
+ * 查询自己收藏的图片列表
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#collect-getlovepic}
+ *
+ * @access public
+ * @param int $page_no 页数,默认为1。
+ * @param boolean $createToken 是否只返回Token,默认为false。
+ * @return string 如果$createToken=true 返回请求接口的json数据否则只返回Token
+ */
+ function getLovePic($page_no=1,$createToken=false){
+ $url = $this->host."/Collect/";
+ $param['deadline'] = time()+$this->timeout;
+ $param['action'] = 'getlovepic';
+ $param['page_no'] = $page_no;
+ $Token=$this->op_Token->dealParam($param)->createToken();
+ $data['Token']=$Token;
+ return $createToken?$Token:$this->post($url,$data);
+ }
+ /**
+ * 通过图片ID喜欢(收藏)图片
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#collect-addcollect}
+ *
+ * @access public
+ * @param int $id 图片ID。
+ * @param boolean $createToken 是否只返回Token,默认为false。
+ * @return string 如果$createToken=true 返回请求接口的json数据否则只返回Token
+ */
+ function addCollect($id,$createToken=false){
+ $url = $this->host."/Collect/";
+ $param['deadline'] = time()+$this->timeout;
+ $param['action'] = 'addcollect';
+ $param['id']=$id;
+ $Token=$this->op_Token->dealParam($param)->createToken();
+ $data['Token']=$Token;
+ return $createToken?$Token:$this->post($url,$data);
+ }
+ /**
+ * 通过图片ID取消喜欢(取消收藏)图片
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#collect-delcollect}
+ *
+ * @access public
+ * @param int $id 图片ID。
+ * @param boolean $createToken 是否只返回Token,默认为false。
+ * @return string 如果$createToken=true 返回请求接口的json数据否则只返回Token
+ */
+ function delCollect($id,$createToken=false){
+ $url = $this->host."/Collect/";
+ $param['deadline'] = time()+$this->timeout;
+ $param['action'] = 'delcollect';
+ $param['id']=$id;
+ $Token=$this->op_Token->dealParam($param)->createToken();
+ $data['Token']=$Token;
+ return $createToken?$Token:$this->post($url,$data);
+ }
+ /**
+ * 通过相册ID分页查询相册中的图片 每页30张图片
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#list-album}
+ *
+ * @access public
+ * @param int $aid 相册ID。
+ * @param int $page_no 页数,默认为1。
+ * @param boolean $createToken 是否只返回Token,默认为false。
+ * @return string 如果$createToken=true 返回请求接口的json数据否则只返回Token
+ */
+
+ function getAlbumPicByAid($aid,$page_no=1,$createToken=false){
+ $url = $this->host."/List/";
+ $param['deadline'] = time()+$this->timeout;
+ $param['action'] = 'album';
+ $param['aid'] = $aid;
+ $param['page_no'] = $page_no;
+ $Token=$this->op_Token->dealParam($param)->createToken();
+ $data['Token']=$Token;
+ return $createToken?$Token:$this->post($url,$data);
+ }
+ /**
+ * 查询所有的分类
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#catalog-getall}
+ *
+ * @access public
+ * @param boolean $createToken 是否只返回Token,默认为false。
+ * @return string 如果$createToken=true 返回请求接口的json数据否则只返回Token
+ */
+ function getCatalog($createToken=false){
+ $url = $this->host."/Catalog/";
+ $param['deadline'] = time()+$this->timeout;
+ $param['action'] = 'getall';
+ $Token=$this->op_Token->dealParam($param)->createToken();
+ $data['Token']=$Token;
+ return $createToken?$Token:$this->post($url,$data);
+ }
+ /**
+ * 创建相册
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#album-create}
+ *
+ * @access public
+ * @param string $albumname 相册名称。
+ * @param boolean $createToken 是否只返回Token,默认为false。
+ * @return string 如果$createToken=true 返回请求接口的json数据否则只返回Token
+ */
+ function createAlbum($albumname,$createToken=false){
+ $url = $this->host."/Album/";
+ $param['deadline'] = time()+$this->timeout;
+ $param['action'] = 'create';
+ $param['albumname'] = $albumname;
+ $Token=$this->op_Token->dealParam($param)->createToken();
+ $data['Token']=$Token;
+ return $createToken?$Token:$this->post($url,$data);
+ }
+ /**
+ * 编辑相册
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#album-editalbum}
+ *
+ * @access public
+ * @param int $aid 相册ID。
+ * @param string $albumname 相册名称。
+ * @param boolean $createToken 是否只返回Token,默认为false。
+ * @return string 如果$createToken=true 返回请求接口的json数据否则只返回Token
+ */
+ function editAlbum($aid,$albumname,$createToken=false){
+ $url = $this->host."/Album/";
+ $param['deadline'] = time()+$this->timeout;
+ $param['action'] = 'editalbum';
+ $param['aid'] = $aid;
+ $param['albumname'] = $albumname;
+ $Token=$this->op_Token->dealParam($param)->createToken();
+ $data['Token']=$Token;
+ return $createToken?$Token:$this->post($url,$data);
+ }
+ /**
+ * 通过相册ID删除相册(只能删除自己的相册 如果只有一个相册,不能删除)
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#album-delalbum}
+ *
+ * @access public
+ * @param int $aid 相册ID。
+ * @param boolean $createToken 是否只返回Token,默认为false。
+ * @return string 如果$createToken=true 返回请求接口的json数据否则只返回Token
+ */
+ function delAlbum($aid,$createToken=false){
+ $url = $this->host."/Album/";
+ $param['deadline'] = time()+$this->timeout;
+ $param['action'] = 'delalbum';
+ $param['aid'] = $aid;
+ $Token=$this->op_Token->dealParam($param)->createToken();
+ $data['Token']=$Token;
+ return $createToken?$Token:$this->post($url,$data);
+ }
+ /**
+ * 通过一组图片ID 查询图片信息
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#list-getpicbyids}
+ *
+ * @access public
+ * @param mix $ids 图片ID数组。(1.多个ID用逗号隔开 2.传入数组)
+ * @param boolean $createToken 是否只返回Token,默认为false。
+ * @return string 如果$createToken=true 返回请求接口的json数据否则只返回Token
+ */
+ function getPicByIds($ids,$createToken=false){
+ $stringid='';
+ if(is_array($ids)){
+ foreach ($ids as $k => $v) {
+ $stringid.=$v.',';
+ }
+ $stringid=substr($stringid,0,-1);
+ }else{
+ $stringid=$ids;
+ }
+ $url = $this->host."/List/";
+ $param['deadline'] = time()+$this->timeout;
+ $param['action'] = 'getpicbyids';
+ $param['ids'] = $stringid;
+ $Token=$this->op_Token->dealParam($param)->createToken();
+ $data['Token']=$Token;
+ return $createToken?$Token:$this->post($url,$data);
+ }
+ /**
+ * 上传单个文件到贴图库
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#upload}
+ *
+ * @access public
+ * @param int $aid 相册ID
+ * @param array $file 上传的文件。
+ * @return string 如果$file!=null 返回请求接口的json数据否则只返回Token
+ */
+ function uploadFile($aid,$file=null,$filename=null){
+ $url = $this->upload_host;
+ $param['deadline'] = time()+$this->timeout;
+ $param['aid'] = $aid;
+ $Token=$this->op_Token->dealParam($param)->createToken();
+ $data['Token']=$Token;
+ $data['file']='@'.$file.(empty($filename)?'':(';filename='.$filename));
+ return empty($file)?$Token:$this->post($url,$data);
+ }
+ /**
+ * 上传多个文件到贴图库
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#upload}
+ *
+ * @access public
+ * @param int $aid 相册ID
+ * @param string $filename 文件域名字
+ * @return mixed 返回请求接口的json 如果文件域不存在文件则返回NULL
+ */
+ function curlUpFile($aid,$filename){
+ if(is_array($_FILES[$filename]['tmp_name'])){
+ foreach ($_FILES[$filename]['tmp_name'] as $k => $v) {
+ if(!empty($v)){
+ $userfile=$_FILES[$filename]['name'][$k];
+ $res[]=json_decode($this->uploadFile($aid,$v,$userfile));
+ }
+ }
+ }else{
+ $res=json_decode($this->uploadFile($aid,$_FILES[$filename]['tmp_name'],$_FILES[$filename]['name']));
+ }
+ return json_encode($res);
+ }
+ /**
+ * 上传网络文件到贴图库 (只支持单个连接)
+ *
+ * 对应API:{@link http://open.tietuku.com/doc#upload-url}
+ *
+ * @access public
+ * @param int $aid 相册ID
+ * @param string $fileurl 网络图片地址
+ * @return string 如果$fileurl!=null 返回请求接口的json数据否则只返回Token
+ */
+ function uploadFromWeb($aid,$fileurl=null){
+ $url = $this->upload_host;
+ $param['deadline'] = time()+$this->timeout;
+ $param['aid'] = $aid;
+ $param['from'] = 'web';
+ $Token=$this->op_Token->dealParam($param)->createToken();
+ $data['Token']=$Token;
+ $data['fileurl']=$fileurl;
+ return empty($fileurl)?$Token:$this->post($url,$data);
+ }
+ /**
+ * 对接口post数据
+ *
+ *
+ * @access public
+ * @param string $url 接口请求地址。
+ * @param array $data 需要post的数据
+ * @return string 返回的json数据
+ */
+ function post($url,$post_data){
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_TIMEOUT,$this->CURLtimeout);
+ curl_setopt($ch, CURLOPT_POST, 1);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
+ $output = curl_exec($ch);
+ curl_close($ch);
+ return $output;
+ }
+
+}
\ No newline at end of file
diff --git a/Framework/Library/Think/Upload/Driver/Upyun.class.php b/Framework/Library/Think/Upload/Driver/Upyun.class.php
new file mode 100644
index 00000000..96645985
--- /dev/null
+++ b/Framework/Library/Think/Upload/Driver/Upyun.class.php
@@ -0,0 +1,230 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think\Upload\Driver;
+
+class Upyun
+{
+ /**
+ * 上传文件根目录
+ * @var string
+ */
+ private $rootPath;
+
+ /**
+ * 上传错误信息
+ * @var string
+ */
+ private $error = '';
+
+ private $config = array(
+ 'host' => '', //又拍云服务器
+ 'username' => '', //又拍云用户
+ 'password' => '', //又拍云密码
+ 'bucket' => '', //空间名称
+ 'timeout' => 90, //超时时间
+ );
+
+ /**
+ * 构造函数,用于设置上传根路径
+ * @param array $config FTP配置
+ */
+ public function __construct($config)
+ {
+ /* 默认FTP配置 */
+ $this->config = array_merge($this->config, $config);
+ $this->config['password'] = md5($this->config['password']);
+ }
+
+ /**
+ * 检测上传根目录(又拍云上传时支持自动创建目录,直接返回)
+ * @param string $rootpath 根目录
+ * @return boolean true-检测通过,false-检测失败
+ */
+ public function checkRootPath($rootpath)
+ {
+ /* 设置根目录 */
+ $this->rootPath = trim($rootpath, './') . '/';
+ return true;
+ }
+
+ /**
+ * 检测上传目录(又拍云上传时支持自动创建目录,直接返回)
+ * @param string $savepath 上传目录
+ * @return boolean 检测结果,true-通过,false-失败
+ */
+ public function checkSavePath($savepath)
+ {
+ return true;
+ }
+
+ /**
+ * 创建文件夹 (又拍云上传时支持自动创建目录,直接返回)
+ * @param string $savepath 目录名称
+ * @return boolean true-创建成功,false-创建失败
+ */
+ public function mkdir($savepath)
+ {
+ return true;
+ }
+
+ /**
+ * 保存指定文件
+ * @param array $file 保存的文件信息
+ * @param boolean $replace 同名文件是否覆盖
+ * @return boolean 保存状态,true-成功,false-失败
+ */
+ public function save($file, $replace = true)
+ {
+ $header['Content-Type'] = $file['type'];
+ $header['Content-MD5'] = $file['md5'];
+ $header['Mkdir'] = 'true';
+ $resource = fopen($file['tmp_name'], 'r');
+
+ $save = $this->rootPath . $file['savepath'] . $file['savename'];
+ $data = $this->request($save, 'PUT', $header, $resource);
+ return false === $data ? false : true;
+ }
+
+ /**
+ * 获取最后一次上传错误信息
+ * @return string 错误信息
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * 请求又拍云服务器
+ * @param string $path 请求的PATH
+ * @param string $method 请求方法
+ * @param array $headers 请求header
+ * @param resource $body 上传文件资源
+ * @return boolean
+ */
+ private function request($path, $method, $headers = null, $body = null)
+ {
+ $uri = "/{$this->config['bucket']}/{$path}";
+ $ch = curl_init($this->config['host'] . $uri);
+
+ $_headers = array('Expect:');
+ if (!is_null($headers) && is_array($headers)) {
+ foreach ($headers as $k => $v) {
+ array_push($_headers, "{$k}: {$v}");
+ }
+ }
+
+ $length = 0;
+ $date = gmdate('D, d M Y H:i:s \G\M\T');
+
+ if (!is_null($body)) {
+ if (is_resource($body)) {
+ fseek($body, 0, SEEK_END);
+ $length = ftell($body);
+ fseek($body, 0);
+
+ array_push($_headers, "Content-Length: {$length}");
+ curl_setopt($ch, CURLOPT_INFILE, $body);
+ curl_setopt($ch, CURLOPT_INFILESIZE, $length);
+ } else {
+ $length = @strlen($body);
+ array_push($_headers, "Content-Length: {$length}");
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
+ }
+ } else {
+ array_push($_headers, "Content-Length: {$length}");
+ }
+
+ array_push($_headers, 'Authorization: ' . $this->sign($method, $uri, $date, $length));
+ array_push($_headers, "Date: {$date}");
+
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $_headers);
+ curl_setopt($ch, CURLOPT_TIMEOUT, $this->config['timeout']);
+ curl_setopt($ch, CURLOPT_HEADER, 1);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
+ curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
+
+ if ('PUT' == $method || 'POST' == $method) {
+ curl_setopt($ch, CURLOPT_POST, 1);
+ } else {
+ curl_setopt($ch, CURLOPT_POST, 0);
+ }
+
+ if ('HEAD' == $method) {
+ curl_setopt($ch, CURLOPT_NOBODY, true);
+ }
+
+ $response = curl_exec($ch);
+ $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ curl_close($ch);
+ list($header, $body) = explode("\r\n\r\n", $response, 2);
+
+ if (200 == $status) {
+ if ('GET' == $method) {
+ return $body;
+ } else {
+ $data = $this->response($header);
+ return count($data) > 0 ? $data : true;
+ }
+ } else {
+ $this->error($header);
+ return false;
+ }
+ }
+
+ /**
+ * 获取响应数据
+ * @param string $text 响应头字符串
+ * @return array 响应数据列表
+ */
+ private function response($text)
+ {
+ $headers = explode("\r\n", $text);
+ $items = array();
+ foreach ($headers as $header) {
+ $header = trim($header);
+ if (strpos($header, 'x-upyun') !== false) {
+ list($k, $v) = explode(':', $header);
+ $items[trim($k)] = in_array(substr($k, 8, 5), array('width', 'heigh', 'frame')) ? intval($v) : trim($v);
+ }
+ }
+ return $items;
+ }
+
+ /**
+ * 生成请求签名
+ * @param string $method 请求方法
+ * @param string $uri 请求URI
+ * @param string $date 请求时间
+ * @param integer $length 请求内容大小
+ * @return string 请求签名
+ */
+ private function sign($method, $uri, $date, $length)
+ {
+ $sign = "{$method}&{$uri}&{$date}&{$length}&{$this->config['password']}";
+ return 'UpYun ' . $this->config['username'] . ':' . md5($sign);
+ }
+
+ /**
+ * 获取请求错误信息
+ * @param string $header 请求返回头信息
+ */
+ private function error($header)
+ {
+ list($status, $stash) = explode("\r\n", $header, 2);
+ list($v, $code, $message) = explode(" ", $status, 3);
+ $message = is_null($message) ? 'File Not Found' : "[{$status}]:{$message}";
+ $this->error = $message;
+ }
+
+}
diff --git a/Framework/Library/Think/Verify.class.php b/Framework/Library/Think/Verify.class.php
new file mode 100644
index 00000000..036457bc
--- /dev/null
+++ b/Framework/Library/Think/Verify.class.php
@@ -0,0 +1,305 @@
+
+// +----------------------------------------------------------------------
+
+namespace Think;
+
+class Verify
+{
+ protected $config = array(
+ 'seKey' => 'ThinkPHP.CN', // 验证码加密密钥
+ 'codeSet' => '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY', // 验证码字符集合
+ 'expire' => 1800, // 验证码过期时间(s)
+ 'useZh' => false, // 使用中文验证码
+ 'zhSet' => '们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书术状厂须离再目海交权且儿青才证低越际八试规斯近注办布门铁需走议县兵固除般引齿千胜细影济白格效置推空配刀叶率述今选养德话查差半敌始片施响收华觉备名红续均药标记难存测士身紧液派准斤角降维板许破述技消底床田势端感往神便贺村构照容非搞亚磨族火段算适讲按值美态黄易彪服早班麦削信排台声该击素张密害侯草何树肥继右属市严径螺检左页抗苏显苦英快称坏移约巴材省黑武培著河帝仅针怎植京助升王眼她抓含苗副杂普谈围食射源例致酸旧却充足短划剂宣环落首尺波承粉践府鱼随考刻靠够满夫失包住促枝局菌杆周护岩师举曲春元超负砂封换太模贫减阳扬江析亩木言球朝医校古呢稻宋听唯输滑站另卫字鼓刚写刘微略范供阿块某功套友限项余倒卷创律雨让骨远帮初皮播优占死毒圈伟季训控激找叫云互跟裂粮粒母练塞钢顶策双留误础吸阻故寸盾晚丝女散焊功株亲院冷彻弹错散商视艺灭版烈零室轻血倍缺厘泵察绝富城冲喷壤简否柱李望盘磁雄似困巩益洲脱投送奴侧润盖挥距触星松送获兴独官混纪依未突架宽冬章湿偏纹吃执阀矿寨责熟稳夺硬价努翻奇甲预职评读背协损棉侵灰虽矛厚罗泥辟告卵箱掌氧恩爱停曾溶营终纲孟钱待尽俄缩沙退陈讨奋械载胞幼哪剥迫旋征槽倒握担仍呀鲜吧卡粗介钻逐弱脚怕盐末阴丰雾冠丙街莱贝辐肠付吉渗瑞惊顿挤秒悬姆烂森糖圣凹陶词迟蚕亿矩康遵牧遭幅园腔订香肉弟屋敏恢忘编印蜂急拿扩伤飞露核缘游振操央伍域甚迅辉异序免纸夜乡久隶缸夹念兰映沟乙吗儒杀汽磷艰晶插埃燃欢铁补咱芽永瓦倾阵碳演威附牙芽永瓦斜灌欧献顺猪洋腐请透司危括脉宜笑若尾束壮暴企菜穗楚汉愈绿拖牛份染既秋遍锻玉夏疗尖殖井费州访吹荣铜沿替滚客召旱悟刺脑措贯藏敢令隙炉壳硫煤迎铸粘探临薄旬善福纵择礼愿伏残雷延烟句纯渐耕跑泽慢栽鲁赤繁境潮横掉锥希池败船假亮谓托伙哲怀割摆贡呈劲财仪沉炼麻罪祖息车穿货销齐鼠抽画饲龙库守筑房歌寒喜哥洗蚀废纳腹乎录镜妇恶脂庄擦险赞钟摇典柄辩竹谷卖乱虚桥奥伯赶垂途额壁网截野遗静谋弄挂课镇妄盛耐援扎虑键归符庆聚绕摩忙舞遇索顾胶羊湖钉仁音迹碎伸灯避泛亡答勇频皇柳哈揭甘诺概宪浓岛袭谁洪谢炮浇斑讯懂灵蛋闭孩释乳巨徒私银伊景坦累匀霉杜乐勒隔弯绩招绍胡呼痛峰零柴簧午跳居尚丁秦稍追梁折耗碱殊岗挖氏刃剧堆赫荷胸衡勤膜篇登驻案刊秧缓凸役剪川雪链渔啦脸户洛孢勃盟买杨宗焦赛旗滤硅炭股坐蒸凝竟陷枪黎救冒暗洞犯筒您宋弧爆谬涂味津臂障褐陆啊健尊豆拔莫抵桑坡缝警挑污冰柬嘴啥饭塑寄赵喊垫丹渡耳刨虎笔稀昆浪萨茶滴浅拥穴覆伦娘吨浸袖珠雌妈紫戏塔锤震岁貌洁剖牢锋疑霸闪埔猛诉刷狠忽灾闹乔唐漏闻沈熔氯荒茎男凡抢像浆旁玻亦忠唱蒙予纷捕锁尤乘乌智淡允叛畜俘摸锈扫毕璃宝芯爷鉴秘净蒋钙肩腾枯抛轨堂拌爸循诱祝励肯酒绳穷塘燥泡袋朗喂铝软渠颗惯贸粪综墙趋彼届墨碍启逆卸航衣孙龄岭骗休借', // 中文验证码字符串
+ 'useImgBg' => false, // 使用背景图片
+ 'fontSize' => 25, // 验证码字体大小(px)
+ 'useCurve' => true, // 是否画混淆曲线
+ 'useNoise' => true, // 是否添加杂点
+ 'imageH' => 0, // 验证码图片高度
+ 'imageW' => 0, // 验证码图片宽度
+ 'length' => 5, // 验证码位数
+ 'fontttf' => '', // 验证码字体,不设置随机获取
+ 'bg' => array(243, 251, 254), // 背景颜色
+ 'reset' => true, // 验证成功后是否重置
+ );
+
+ private $_image = null; // 验证码图片实例
+ private $_color = null; // 验证码字体颜色
+
+ /**
+ * 架构方法 设置参数
+ * @access public
+ * @param array $config 配置参数
+ */
+ public function __construct($config = array())
+ {
+ $this->config = array_merge($this->config, $config);
+ }
+
+ /**
+ * 使用 $this->name 获取配置
+ * @access public
+ * @param string $name 配置名称
+ * @return multitype 配置值
+ */
+ public function __get($name)
+ {
+ return $this->config[$name];
+ }
+
+ /**
+ * 设置验证码配置
+ * @access public
+ * @param string $name 配置名称
+ * @param string $value 配置值
+ * @return void
+ */
+ public function __set($name, $value)
+ {
+ if (isset($this->config[$name])) {
+ $this->config[$name] = $value;
+ }
+ }
+
+ /**
+ * 检查配置
+ * @access public
+ * @param string $name 配置名称
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ return isset($this->config[$name]);
+ }
+
+ /**
+ * 验证验证码是否正确
+ * @access public
+ * @param string $code 用户验证码
+ * @param string $id 验证码标识
+ * @return bool 用户验证码是否正确
+ */
+ public function check($code, $id = '')
+ {
+ $key = $this->authcode($this->seKey) . $id;
+ // 验证码不能为空
+ $secode = session($key);
+ if (empty($code) || empty($secode)) {
+ return false;
+ }
+ // session 过期
+ if (NOW_TIME - $secode['verify_time'] > $this->expire) {
+ session($key, null);
+ return false;
+ }
+
+ if ($this->authcode(strtoupper($code)) == $secode['verify_code']) {
+ $this->reset && session($key, null);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 输出验证码并把验证码的值保存的session中
+ * 验证码保存到session的格式为: array('verify_code' => '验证码值', 'verify_time' => '验证码创建时间');
+ * @access public
+ * @param string $id 要生成验证码的标识
+ * @return void
+ */
+ public function entry($id = '')
+ {
+ // 图片宽(px)
+ $this->imageW || $this->imageW = $this->length * $this->fontSize * 1.5 + $this->length * $this->fontSize / 2;
+ // 图片高(px)
+ $this->imageH || $this->imageH = $this->fontSize * 2.5;
+ // 建立一幅 $this->imageW x $this->imageH 的图像
+ $this->_image = imagecreate($this->imageW, $this->imageH);
+ // 设置背景
+ imagecolorallocate($this->_image, $this->bg[0], $this->bg[1], $this->bg[2]);
+
+ // 验证码字体随机颜色
+ $this->_color = imagecolorallocate($this->_image, mt_rand(1, 150), mt_rand(1, 150), mt_rand(1, 150));
+ // 验证码使用随机字体
+ $ttfPath = dirname(__FILE__) . '/Verify/' . ($this->useZh ? 'zhttfs' : 'ttfs') . '/';
+
+ if (empty($this->fontttf)) {
+ $dir = dir($ttfPath);
+ $ttfs = array();
+ while (false !== ($file = $dir->read())) {
+ if ('.' != $file[0] && substr($file, -4) == '.ttf') {
+ $ttfs[] = $file;
+ }
+ }
+ $dir->close();
+ $this->fontttf = $ttfs[array_rand($ttfs)];
+ }
+ $this->fontttf = $ttfPath . $this->fontttf;
+
+ if ($this->useImgBg) {
+ $this->_background();
+ }
+
+ if ($this->useNoise) {
+ // 绘杂点
+ $this->_writeNoise();
+ }
+ if ($this->useCurve) {
+ // 绘干扰线
+ $this->_writeCurve();
+ }
+
+ // 绘验证码
+ $code = array(); // 验证码
+ $codeNX = 0; // 验证码第N个字符的左边距
+ if ($this->useZh) {
+ // 中文验证码
+ for ($i = 0; $i < $this->length; $i++) {
+ $code[$i] = iconv_substr($this->zhSet, floor(mt_rand(0, mb_strlen($this->zhSet, 'utf-8') - 1)), 1, 'utf-8');
+ imagettftext($this->_image, $this->fontSize, mt_rand(-40, 40), $this->fontSize * ($i + 1) * 1.5, $this->fontSize + mt_rand(10, 20), $this->_color, $this->fontttf, $code[$i]);
+ }
+ } else {
+ for ($i = 0; $i < $this->length; $i++) {
+ $code[$i] = $this->codeSet[mt_rand(0, strlen($this->codeSet) - 1)];
+ $codeNX += mt_rand($this->fontSize * 1.2, $this->fontSize * 1.6);
+ imagettftext($this->_image, $this->fontSize, mt_rand(-40, 40), $codeNX, $this->fontSize * 1.6, $this->_color, $this->fontttf, $code[$i]);
+ }
+ }
+
+ // 保存验证码
+ $key = $this->authcode($this->seKey);
+ $code = $this->authcode(strtoupper(implode('', $code)));
+ $secode = array();
+ $secode['verify_code'] = $code; // 把校验码保存到session
+ $secode['verify_time'] = NOW_TIME; // 验证码创建时间
+ session($key . $id, $secode);
+
+ header('Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate');
+ header('Cache-Control: post-check=0, pre-check=0', false);
+ header('Pragma: no-cache');
+ header("content-type: image/png");
+
+ // 输出图像
+ imagepng($this->_image);
+ imagedestroy($this->_image);
+ }
+
+ /**
+ * 画一条由两条连在一起构成的随机正弦函数曲线作干扰线(你可以改成更帅的曲线函数)
+ *
+ * 高中的数学公式咋都忘了涅,写出来
+ * 正弦型函数解析式:y=Asin(ωx+φ)+b
+ * 各常数值对函数图像的影响:
+ * A:决定峰值(即纵向拉伸压缩的倍数)
+ * b:表示波形在Y轴的位置关系或纵向移动距离(上加下减)
+ * φ:决定波形与X轴位置关系或横向移动距离(左加右减)
+ * ω:决定周期(最小正周期T=2π/∣ω∣)
+ *
+ */
+ private function _writeCurve()
+ {
+ $px = $py = 0;
+
+ // 曲线前部分
+ $A = mt_rand(1, $this->imageH / 2); // 振幅
+ $b = mt_rand(-$this->imageH / 4, $this->imageH / 4); // Y轴方向偏移量
+ $f = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量
+ $T = mt_rand($this->imageH, $this->imageW * 2); // 周期
+ $w = (2 * M_PI) / $T;
+
+ $px1 = 0; // 曲线横坐标起始位置
+ $px2 = mt_rand($this->imageW / 2, $this->imageW * 0.8); // 曲线横坐标结束位置
+
+ for ($px = $px1; $px <= $px2; $px = $px + 1) {
+ if (0 != $w) {
+ $py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b
+ $i = (int) ($this->fontSize / 5);
+ while ($i > 0) {
+ imagesetpixel($this->_image, $px + $i, $py + $i, $this->_color); // 这里(while)循环画像素点比imagettftext和imagestring用字体大小一次画出(不用这while循环)性能要好很多
+ $i--;
+ }
+ }
+ }
+
+ // 曲线后部分
+ $A = mt_rand(1, $this->imageH / 2); // 振幅
+ $f = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量
+ $T = mt_rand($this->imageH, $this->imageW * 2); // 周期
+ $w = (2 * M_PI) / $T;
+ $b = $py - $A * sin($w * $px + $f) - $this->imageH / 2;
+ $px1 = $px2;
+ $px2 = $this->imageW;
+
+ for ($px = $px1; $px <= $px2; $px = $px + 1) {
+ if (0 != $w) {
+ $py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b
+ $i = (int) ($this->fontSize / 5);
+ while ($i > 0) {
+ imagesetpixel($this->_image, $px + $i, $py + $i, $this->_color);
+ $i--;
+ }
+ }
+ }
+ }
+
+ /**
+ * 画杂点
+ * 往图片上写不同颜色的字母或数字
+ */
+ private function _writeNoise()
+ {
+ $codeSet = '2345678abcdefhijkmnpqrstuvwxyz';
+ for ($i = 0; $i < 10; $i++) {
+ //杂点颜色
+ $noiseColor = imagecolorallocate($this->_image, mt_rand(150, 225), mt_rand(150, 225), mt_rand(150, 225));
+ for ($j = 0; $j < 5; $j++) {
+ // 绘杂点
+ imagestring($this->_image, 5, mt_rand(-10, $this->imageW), mt_rand(-10, $this->imageH), $codeSet[mt_rand(0, 29)], $noiseColor);
+ }
+ }
+ }
+
+ /**
+ * 绘制背景图片
+ * 注:如果验证码输出图片比较大,将占用比较多的系统资源
+ */
+ private function _background()
+ {
+ $path = dirname(__FILE__) . '/Verify/bgs/';
+ $dir = dir($path);
+
+ $bgs = array();
+ while (false !== ($file = $dir->read())) {
+ if ('.' != $file[0] && substr($file, -4) == '.jpg') {
+ $bgs[] = $path . $file;
+ }
+ }
+ $dir->close();
+
+ $gb = $bgs[array_rand($bgs)];
+
+ list($width, $height) = @getimagesize($gb);
+ // Resample
+ $bgImage = @imagecreatefromjpeg($gb);
+ @imagecopyresampled($this->_image, $bgImage, 0, 0, 0, 0, $this->imageW, $this->imageH, $width, $height);
+ @imagedestroy($bgImage);
+ }
+
+ /* 加密验证码 */
+ private function authcode($str)
+ {
+ $key = substr(md5($this->seKey), 5, 8);
+ $str = substr(md5($str), 8, 10);
+ return md5($key . $str);
+ }
+
+}
diff --git a/Framework/Library/Think/Verify/bgs/1.jpg b/Framework/Library/Think/Verify/bgs/1.jpg
new file mode 100644
index 00000000..d417136b
Binary files /dev/null and b/Framework/Library/Think/Verify/bgs/1.jpg differ
diff --git a/Framework/Library/Think/Verify/bgs/2.jpg b/Framework/Library/Think/Verify/bgs/2.jpg
new file mode 100644
index 00000000..56640bde
Binary files /dev/null and b/Framework/Library/Think/Verify/bgs/2.jpg differ
diff --git a/Framework/Library/Think/Verify/bgs/3.jpg b/Framework/Library/Think/Verify/bgs/3.jpg
new file mode 100644
index 00000000..83e5bd90
Binary files /dev/null and b/Framework/Library/Think/Verify/bgs/3.jpg differ
diff --git a/Framework/Library/Think/Verify/bgs/4.jpg b/Framework/Library/Think/Verify/bgs/4.jpg
new file mode 100644
index 00000000..97a3721b
Binary files /dev/null and b/Framework/Library/Think/Verify/bgs/4.jpg differ
diff --git a/Framework/Library/Think/Verify/bgs/5.jpg b/Framework/Library/Think/Verify/bgs/5.jpg
new file mode 100644
index 00000000..220a17a2
Binary files /dev/null and b/Framework/Library/Think/Verify/bgs/5.jpg differ
diff --git a/Framework/Library/Think/Verify/bgs/6.jpg b/Framework/Library/Think/Verify/bgs/6.jpg
new file mode 100644
index 00000000..be53ea0a
Binary files /dev/null and b/Framework/Library/Think/Verify/bgs/6.jpg differ
diff --git a/Framework/Library/Think/Verify/bgs/7.jpg b/Framework/Library/Think/Verify/bgs/7.jpg
new file mode 100644
index 00000000..fbf537fa
Binary files /dev/null and b/Framework/Library/Think/Verify/bgs/7.jpg differ
diff --git a/Framework/Library/Think/Verify/bgs/8.jpg b/Framework/Library/Think/Verify/bgs/8.jpg
new file mode 100644
index 00000000..e10cf281
Binary files /dev/null and b/Framework/Library/Think/Verify/bgs/8.jpg differ
diff --git a/Framework/Library/Think/Verify/ttfs/1.ttf b/Framework/Library/Think/Verify/ttfs/1.ttf
new file mode 100644
index 00000000..d4ee1558
Binary files /dev/null and b/Framework/Library/Think/Verify/ttfs/1.ttf differ
diff --git a/Framework/Library/Think/Verify/ttfs/2.ttf b/Framework/Library/Think/Verify/ttfs/2.ttf
new file mode 100644
index 00000000..3a452b68
Binary files /dev/null and b/Framework/Library/Think/Verify/ttfs/2.ttf differ
diff --git a/Framework/Library/Think/Verify/ttfs/3.ttf b/Framework/Library/Think/Verify/ttfs/3.ttf
new file mode 100644
index 00000000..d07a4d93
Binary files /dev/null and b/Framework/Library/Think/Verify/ttfs/3.ttf differ
diff --git a/Framework/Library/Think/Verify/ttfs/4.ttf b/Framework/Library/Think/Verify/ttfs/4.ttf
new file mode 100644
index 00000000..54a14ed1
Binary files /dev/null and b/Framework/Library/Think/Verify/ttfs/4.ttf differ
diff --git a/Framework/Library/Think/Verify/ttfs/5.ttf b/Framework/Library/Think/Verify/ttfs/5.ttf
new file mode 100644
index 00000000..d672876d
Binary files /dev/null and b/Framework/Library/Think/Verify/ttfs/5.ttf differ
diff --git a/Framework/Library/Think/Verify/ttfs/6.ttf b/Framework/Library/Think/Verify/ttfs/6.ttf
new file mode 100644
index 00000000..7f183e20
Binary files /dev/null and b/Framework/Library/Think/Verify/ttfs/6.ttf differ
diff --git a/Framework/Library/Think/Verify/zhttfs/1.ttf b/Framework/Library/Think/Verify/zhttfs/1.ttf
new file mode 100644
index 00000000..1c14f7fa
Binary files /dev/null and b/Framework/Library/Think/Verify/zhttfs/1.ttf differ
diff --git a/Framework/Library/Think/View.class.php b/Framework/Library/Think/View.class.php
new file mode 100644
index 00000000..304566b8
--- /dev/null
+++ b/Framework/Library/Think/View.class.php
@@ -0,0 +1,270 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * ThinkPHP 视图类
+ */
+class View
+{
+ /**
+ * 模板输出变量
+ * @var tVar
+ * @access protected
+ */
+ protected $tVar = array();
+
+ /**
+ * 模板主题
+ * @var theme
+ * @access protected
+ */
+ protected $theme = '';
+
+ /**
+ * 模板变量赋值
+ * @access public
+ * @param mixed $name
+ * @param mixed $value
+ */
+ public function assign($name, $value = '')
+ {
+ if (is_array($name)) {
+ $this->tVar = array_merge($this->tVar, $name);
+ } else {
+ $this->tVar[$name] = $value;
+ }
+ }
+
+ /**
+ * 取得模板变量的值
+ * @access public
+ * @param string $name
+ * @return mixed
+ */
+ public function get($name = '')
+ {
+ if ('' === $name) {
+ return $this->tVar;
+ }
+ return isset($this->tVar[$name]) ? $this->tVar[$name] : false;
+ }
+
+ /**
+ * 加载模板和页面输出 可以返回输出内容
+ * @access public
+ * @param string $templateFile 模板文件名
+ * @param string $charset 模板输出字符集
+ * @param string $contentType 输出类型
+ * @param string $content 模板输出内容
+ * @param string $prefix 模板缓存前缀
+ * @return mixed
+ */
+ public function display($templateFile = '', $charset = '', $contentType = '', $content = '', $prefix = '')
+ {
+ G('viewStartTime');
+ // 视图开始标签
+ Hook::listen('view_begin', $templateFile);
+ // 解析并获取模板内容
+ $content = $this->fetch($templateFile, $content, $prefix);
+ // 输出模板内容
+ $this->render($content, $charset, $contentType);
+ // 视图结束标签
+ Hook::listen('view_end');
+ }
+
+ /**
+ * 输出内容文本可以包括Html
+ * @access private
+ * @param string $content 输出内容
+ * @param string $charset 模板输出字符集
+ * @param string $contentType 输出类型
+ * @return mixed
+ */
+ private function render($content, $charset = '', $contentType = '')
+ {
+ if (empty($charset)) {
+ $charset = C('DEFAULT_CHARSET');
+ }
+
+ if (empty($contentType)) {
+ $contentType = C('TMPL_CONTENT_TYPE');
+ }
+
+ // 网页字符编码
+ header('Content-Type:' . $contentType . '; charset=' . $charset);
+ header('Cache-control: ' . C('HTTP_CACHE_CONTROL')); // 页面缓存控制
+ header('X-Powered-By:OpenCMF');
+ // 输出模板文件
+ echo $content;
+ }
+
+ /**
+ * 解析和获取模板内容 用于输出
+ * @access public
+ * @param string $templateFile 模板文件名
+ * @param string $content 模板输出内容
+ * @param string $prefix 模板缓存前缀
+ * @return string
+ */
+ public function fetch($templateFile = '', $content = '', $prefix = '')
+ {
+ if (empty($content)) {
+ $templateFile = $this->parseTemplate($templateFile);
+ // 模板文件不存在直接返回
+ if (!is_file($templateFile)) {
+ E(L('_TEMPLATE_NOT_EXIST_') . ':' . $templateFile);
+ }
+
+ } else {
+ defined('THEME_PATH') or define('THEME_PATH', $this->getThemePath());
+ }
+ // 页面缓存
+ ob_start();
+ ob_implicit_flush(0);
+ if ('php' == strtolower(C('TMPL_ENGINE_TYPE'))) {
+ // 使用PHP原生模板
+ if (empty($content)) {
+ if (isset($this->tVar['templateFile'])) {
+ $__template__ = $templateFile;
+ extract($this->tVar, EXTR_OVERWRITE);
+ include $__template__;
+ } else {
+ extract($this->tVar, EXTR_OVERWRITE);
+ include $templateFile;
+ }
+ } elseif (isset($this->tVar['content'])) {
+ $__content__ = $content;
+ extract($this->tVar, EXTR_OVERWRITE);
+ eval('?>' . $__content__);
+ } else {
+ extract($this->tVar, EXTR_OVERWRITE);
+ eval('?>' . $content);
+ }
+ } else {
+ // 视图解析标签
+ $params = array('var' => $this->tVar, 'file' => $templateFile, 'content' => $content, 'prefix' => $prefix);
+ Hook::listen('view_parse', $params);
+ }
+ // 获取并清空缓存
+ $content = ob_get_clean();
+ // 内容过滤标签
+ Hook::listen('view_filter', $content);
+ if (APP_DEBUG && C('PARSE_VAR')) {
+ // debug模式时,将后台分配变量输出到浏览器控制台
+ $parseVar = empty($this->tVar) ? json_encode(array()) : json_encode($this->tVar);
+ $content = $content . '';
+ }
+ // 输出模板文件
+ return $content;
+ }
+
+ /**
+ * 自动定位模板文件
+ * @access protected
+ * @param string $template 模板文件规则
+ * @return string
+ */
+ public function parseTemplate($template = '')
+ {
+ if (is_file($template)) {
+ return $template;
+ }
+ $depr = C('TMPL_FILE_DEPR');
+ $template = str_replace(':', $depr, $template);
+
+ // 获取当前模块
+ $module = MODULE_NAME;
+ if (strpos($template, '@')) {
+ // 跨模块调用模版文件
+ list($module, $template) = explode('@', $template);
+ }
+ // 获取当前主题的模版路径
+ defined('THEME_PATH') or define('THEME_PATH', $this->getThemePath($module));
+
+ // 分析模板文件规则
+ if ('' == $template) {
+ // 如果模板文件名为空 按照默认规则定位
+ $template = CONTROLLER_NAME . $depr . ACTION_NAME;
+ } elseif (false === strpos($template, $depr)) {
+ $template = CONTROLLER_NAME . $depr . $template;
+ }
+ $file = THEME_PATH . $template . C('TMPL_TEMPLATE_SUFFIX');
+ if (C('TMPL_LOAD_DEFAULTTHEME') && THEME_NAME != C('DEFAULT_THEME') && !is_file($file)) {
+ // 找不到当前主题模板的时候定位默认主题中的模板
+ $file = dirname(THEME_PATH) . '/' . C('DEFAULT_THEME') . '/' . $template . C('TMPL_TEMPLATE_SUFFIX');
+ }
+ return $file;
+ }
+
+ /**
+ * 获取当前的模板路径
+ * @access protected
+ * @param string $module 模块名
+ * @return string
+ */
+ protected function getThemePath($module = MODULE_NAME)
+ {
+ // 获取当前主题名称
+ $theme = $this->getTemplateTheme();
+ // 获取当前主题的模版路径
+ $tmplPath = C('VIEW_PATH'); // 模块设置独立的视图目录
+ if (!$tmplPath) {
+ // 定义TMPL_PATH 则改变全局的视图目录到模块之外
+ $tmplPath = defined('TMPL_PATH') ? TMPL_PATH . $module . '/' : APP_PATH . $module . '/' . C('DEFAULT_V_LAYER') . '/';
+ }
+ return $tmplPath . $theme;
+ }
+
+ /**
+ * 设置当前输出的模板主题
+ * @access public
+ * @param mixed $theme 主题名称
+ * @return View
+ */
+ public function theme($theme)
+ {
+ $this->theme = $theme;
+ return $this;
+ }
+
+ /**
+ * 获取当前的模板主题
+ * @access private
+ * @return string
+ */
+ private function getTemplateTheme()
+ {
+ if ($this->theme) {
+ // 指定模板主题
+ $theme = $this->theme;
+ } else {
+ /* 获取模板主题名称 */
+ $theme = C('DEFAULT_THEME');
+ if (C('TMPL_DETECT_THEME')) {
+// 自动侦测模板主题
+ $t = C('VAR_TEMPLATE');
+ if (isset($_GET[$t])) {
+ $theme = $_GET[$t];
+ } elseif (cookie('think_template')) {
+ $theme = cookie('think_template');
+ }
+ if (!in_array($theme, explode(',', C('THEME_LIST')))) {
+ $theme = C('DEFAULT_THEME');
+ }
+ cookie('think_template', $theme, 864000);
+ }
+ }
+ defined('THEME_NAME') || define('THEME_NAME', $theme); // 当前模板主题名称
+ return $theme ? $theme . '/' : '';
+ }
+
+}
diff --git a/Framework/LingYun.php b/Framework/LingYun.php
new file mode 100644
index 00000000..c66d876b
--- /dev/null
+++ b/Framework/LingYun.php
@@ -0,0 +1,100 @@
+
+// +----------------------------------------------------------------------
+
+//----------------------------------
+// ThinkPHP公共入口文件
+//----------------------------------
+
+// 记录开始运行时间
+$GLOBALS['_beginTime'] = microtime(true);
+// 记录内存初始使用
+define('MEMORY_LIMIT_ON', function_exists('memory_get_usage'));
+if (MEMORY_LIMIT_ON) {
+ $GLOBALS['_startUseMems'] = memory_get_usage();
+}
+
+// 版本信息
+const THINK_VERSION = '3.2.3';
+
+// URL 模式定义
+const URL_COMMON = 0; //普通模式
+const URL_PATHINFO = 1; //PATHINFO模式
+const URL_REWRITE = 2; //REWRITE模式
+const URL_COMPAT = 3; // 兼容模式
+
+// 类文件后缀
+const EXT = '.class.php';
+
+// 系统常量定义
+defined('THINK_PATH') or define('THINK_PATH', __DIR__ . '/');
+defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']) . '/');
+defined('APP_STATUS') or define('APP_STATUS', ''); // 应用状态 加载对应的配置文件
+defined('APP_DEBUG') or define('APP_DEBUG', false); // 是否调试模式
+
+if (function_exists('saeAutoLoader')) {
+// 自动识别SAE环境
+ defined('APP_MODE') or define('APP_MODE', 'sae');
+ defined('STORAGE_TYPE') or define('STORAGE_TYPE', 'Sae');
+} else {
+ defined('APP_MODE') or define('APP_MODE', 'common'); // 应用模式 默认为普通模式
+ defined('STORAGE_TYPE') or define('STORAGE_TYPE', 'File'); // 存储类型 默认为File
+}
+
+defined('RUNTIME_PATH') or define('RUNTIME_PATH', APP_PATH . 'Runtime/'); // 系统运行时目录
+defined('LIB_PATH') or define('LIB_PATH', realpath(THINK_PATH . 'Library') . '/'); // 系统核心类库目录
+defined('CORE_PATH') or define('CORE_PATH', LIB_PATH . 'Think/'); // Think类库目录
+defined('BEHAVIOR_PATH') or define('BEHAVIOR_PATH', LIB_PATH . 'Behavior/'); // 行为类库目录
+defined('MODE_PATH') or define('MODE_PATH', THINK_PATH . 'Mode/'); // 系统应用模式目录
+defined('VENDOR_PATH') or define('VENDOR_PATH', LIB_PATH . 'Vendor/'); // 第三方类库目录
+defined('COMMON_PATH') or define('COMMON_PATH', APP_PATH . 'Common/'); // 应用公共目录
+defined('CONF_PATH') or define('CONF_PATH', COMMON_PATH . 'Conf/'); // 应用配置目录
+defined('LANG_PATH') or define('LANG_PATH', COMMON_PATH . 'Lang/'); // 应用语言目录
+defined('HTML_PATH') or define('HTML_PATH', APP_PATH . 'Html/'); // 应用静态目录
+defined('LOG_PATH') or define('LOG_PATH', RUNTIME_PATH . 'Logs/'); // 应用日志目录
+defined('TEMP_PATH') or define('TEMP_PATH', RUNTIME_PATH . 'Temp/'); // 应用缓存目录
+defined('DATA_PATH') or define('DATA_PATH', RUNTIME_PATH . 'Data/'); // 应用数据目录
+defined('CACHE_PATH') or define('CACHE_PATH', RUNTIME_PATH . 'Cache/'); // 应用模板缓存目录
+defined('CONF_EXT') or define('CONF_EXT', '.php'); // 配置文件后缀
+defined('CONF_PARSE') or define('CONF_PARSE', ''); // 配置文件解析方法
+defined('ADDON_PATH') or define('ADDON_PATH', APP_PATH . 'Addon');
+
+// 系统信息
+if (version_compare(PHP_VERSION, '5.4.0', '<')) {
+ ini_set('magic_quotes_runtime', 0);
+ define('MAGIC_QUOTES_GPC', get_magic_quotes_gpc() ? true : false);
+} else {
+ define('MAGIC_QUOTES_GPC', false);
+}
+define('IS_CGI', (0 === strpos(PHP_SAPI, 'cgi') || false !== strpos(PHP_SAPI, 'fcgi')) ? 1 : 0);
+define('IS_WIN', strstr(PHP_OS, 'WIN') ? 1 : 0);
+define('IS_CLI', PHP_SAPI == 'cli' ? 1 : 0);
+
+if (!IS_CLI) {
+ // 当前文件名
+ if (!defined('_PHP_FILE_')) {
+ if (IS_CGI) {
+ //CGI/FASTCGI模式下
+ $_temp = explode('.php', $_SERVER['PHP_SELF']);
+ define('_PHP_FILE_', rtrim(str_replace($_SERVER['HTTP_HOST'], '', $_temp[0] . '.php'), '/'));
+ } else {
+ define('_PHP_FILE_', rtrim($_SERVER['SCRIPT_NAME'], '/'));
+ }
+ }
+ if (!defined('__ROOT__')) {
+ $_root = rtrim(dirname(_PHP_FILE_), '/');
+ define('__ROOT__', (('/' == $_root || '\\' == $_root) ? '' : $_root));
+ }
+}
+
+// 加载核心Think类
+require CORE_PATH . 'Think' . EXT;
+// 应用初始化
+Think\Think::start();
diff --git a/Framework/Mode/Api/App.class.php b/Framework/Mode/Api/App.class.php
new file mode 100644
index 00000000..ccc168b3
--- /dev/null
+++ b/Framework/Mode/Api/App.class.php
@@ -0,0 +1,150 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * ThinkPHP API模式 应用程序类
+ */
+
+class App
+{
+
+ /**
+ * 应用程序初始化
+ * @access public
+ * @return void
+ */
+ public static function init()
+ {
+ // 定义当前请求的系统常量
+ define('NOW_TIME', $_SERVER['REQUEST_TIME']);
+ define('REQUEST_METHOD', $_SERVER['REQUEST_METHOD']);
+ define('IS_GET', REQUEST_METHOD == 'GET' ? true : false);
+ define('IS_POST', REQUEST_METHOD == 'POST' ? true : false);
+ define('IS_PUT', REQUEST_METHOD == 'PUT' ? true : false);
+ define('IS_DELETE', REQUEST_METHOD == 'DELETE' ? true : false);
+ define('IS_AJAX', ((isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') || !empty($_POST[C('VAR_AJAX_SUBMIT')]) || !empty($_GET[C('VAR_AJAX_SUBMIT')])) ? true : false);
+
+ // URL调度
+ Dispatcher::dispatch();
+
+ if (C('REQUEST_VARS_FILTER')) {
+ // 全局安全过滤
+ array_walk_recursive($_GET, 'think_filter');
+ array_walk_recursive($_POST, 'think_filter');
+ array_walk_recursive($_REQUEST, 'think_filter');
+ }
+
+ // 日志目录转换为绝对路径
+ C('LOG_PATH', realpath(LOG_PATH) . '/');
+ // TMPL_EXCEPTION_FILE 改为绝对地址
+ C('TMPL_EXCEPTION_FILE', realpath(C('TMPL_EXCEPTION_FILE')));
+ return;
+ }
+
+ /**
+ * 执行应用程序
+ * @access public
+ * @return void
+ */
+ public static function exec()
+ {
+
+ if (!preg_match('/^[A-Za-z](\/|\w)*$/', CONTROLLER_NAME)) {
+ // 安全检测
+ $module = false;
+ } else {
+ //创建控制器实例
+ $module = A(CONTROLLER_NAME);
+ }
+
+ if (!$module) {
+ // 是否定义Empty控制器
+ $module = A('Empty');
+ if (!$module) {
+ E(L('_CONTROLLER_NOT_EXIST_') . ':' . CONTROLLER_NAME);
+ }
+ }
+
+ // 获取当前操作名 支持动态路由
+ $action = ACTION_NAME;
+
+ try {
+ if (!preg_match('/^[A-Za-z](\w)*$/', $action)) {
+ // 非法操作
+ throw new \ReflectionException();
+ }
+ //执行当前操作
+ $method = new \ReflectionMethod($module, $action);
+ if ($method->isPublic() && !$method->isStatic()) {
+ $class = new \ReflectionClass($module);
+ // URL参数绑定检测
+ if (C('URL_PARAMS_BIND') && $method->getNumberOfParameters() > 0) {
+ switch ($_SERVER['REQUEST_METHOD']) {
+ case 'POST':
+ $vars = array_merge($_GET, $_POST);
+ break;
+ case 'PUT':
+ parse_str(file_get_contents('php://input'), $vars);
+ break;
+ default:
+ $vars = $_GET;
+ }
+ $params = $method->getParameters();
+ $paramsBindType = C('URL_PARAMS_BIND_TYPE');
+ foreach ($params as $param) {
+ $name = $param->getName();
+ if (1 == $paramsBindType && !empty($vars)) {
+ $args[] = array_shift($vars);
+ } elseif (0 == $paramsBindType && isset($vars[$name])) {
+ $args[] = $vars[$name];
+ } elseif ($param->isDefaultValueAvailable()) {
+ $args[] = $param->getDefaultValue();
+ } else {
+ E(L('_PARAM_ERROR_') . ':' . $name);
+ }
+ }
+ array_walk_recursive($args, 'think_filter');
+ $method->invokeArgs($module, $args);
+ } else {
+ $method->invoke($module);
+ }
+ } else {
+ // 操作方法不是Public 抛出异常
+ throw new \ReflectionException();
+ }
+ } catch (\ReflectionException $e) {
+ // 方法调用发生异常后 引导到__call方法处理
+ $method = new \ReflectionMethod($module, '__call');
+ $method->invokeArgs($module, array($action, ''));
+ }
+ return;
+ }
+
+ /**
+ * 运行应用实例 入口文件使用的快捷方法
+ * @access public
+ * @return void
+ */
+ public static function run()
+ {
+ App::init();
+ // Session初始化
+ if (!IS_CLI) {
+ session(C('SESSION_OPTIONS'));
+ }
+ // 记录应用初始化时间
+ G('initTime');
+ App::exec();
+ return;
+ }
+
+}
diff --git a/Framework/Mode/Api/Controller.class.php b/Framework/Mode/Api/Controller.class.php
new file mode 100644
index 00000000..e266f54a
--- /dev/null
+++ b/Framework/Mode/Api/Controller.class.php
@@ -0,0 +1,103 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * ThinkPHP API模式控制器基类
+ */
+abstract class Controller
+{
+
+ /**
+ * 架构函数
+ * @access public
+ */
+ public function __construct()
+ {
+ //控制器初始化
+ if (method_exists($this, '_initialize')) {
+ $this->_initialize();
+ }
+
+ }
+
+ /**
+ * 魔术方法 有不存在的操作的时候执行
+ * @access public
+ * @param string $method 方法名
+ * @param array $args 参数
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ if (0 === strcasecmp($method, ACTION_NAME . C('ACTION_SUFFIX'))) {
+ if (method_exists($this, '_empty')) {
+ // 如果定义了_empty操作 则调用
+ $this->_empty($method, $args);
+ } else {
+ E(L('_ERROR_ACTION_') . ':' . ACTION_NAME);
+ }
+ } else {
+ E(__CLASS__ . ':' . $method . L('_METHOD_NOT_EXIST_'));
+ return;
+ }
+ }
+
+ /**
+ * Ajax方式返回数据到客户端
+ * @access protected
+ * @param mixed $data 要返回的数据
+ * @param String $type AJAX返回数据格式
+ * @return void
+ */
+ protected function ajaxReturn($data, $type = '')
+ {
+ if (empty($type)) {
+ $type = C('DEFAULT_AJAX_RETURN');
+ }
+
+ switch (strtoupper($type)) {
+ case 'JSON':
+ // 返回JSON数据格式到客户端 包含状态信息
+ header('Content-Type:application/json; charset=utf-8');
+ exit(json_encode($data));
+ case 'XML':
+ // 返回xml格式数据
+ header('Content-Type:text/xml; charset=utf-8');
+ exit(xml_encode($data));
+ case 'JSONP':
+ // 返回JSON数据格式到客户端 包含状态信息
+ header('Content-Type:application/json; charset=utf-8');
+ $handler = isset($_GET[C('VAR_JSONP_HANDLER')]) ? $_GET[C('VAR_JSONP_HANDLER')] : C('DEFAULT_JSONP_HANDLER');
+ exit($handler . '(' . json_encode($data) . ');');
+ case 'EVAL':
+ // 返回可执行的js脚本
+ header('Content-Type:text/html; charset=utf-8');
+ exit($data);
+ }
+ }
+
+ /**
+ * Action跳转(URL重定向) 支持指定模块和延时跳转
+ * @access protected
+ * @param string $url 跳转的URL表达式
+ * @param array $params 其它URL参数
+ * @param integer $delay 延时跳转的时间 单位为秒
+ * @param string $msg 跳转提示信息
+ * @return void
+ */
+ protected function redirect($url, $params = array(), $delay = 0, $msg = '')
+ {
+ $url = U($url, $params);
+ redirect($url, $delay, $msg);
+ }
+
+}
diff --git a/Framework/Mode/Api/Dispatcher.class.php b/Framework/Mode/Api/Dispatcher.class.php
new file mode 100644
index 00000000..a946b495
--- /dev/null
+++ b/Framework/Mode/Api/Dispatcher.class.php
@@ -0,0 +1,257 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * ThinkPHP API模式的Dispatcher类
+ * 完成URL解析、路由和调度
+ */
+
+class Dispatcher
+{
+
+ /**
+ * URL映射到控制器
+ * @access public
+ * @return void
+ */
+ public static function dispatch()
+ {
+ $varPath = C('VAR_PATHINFO');
+ $varModule = C('VAR_MODULE');
+ $varController = C('VAR_CONTROLLER');
+ $varAction = C('VAR_ACTION');
+ $urlCase = C('URL_CASE_INSENSITIVE');
+ if (isset($_GET[$varPath])) {
+ // 判断URL里面是否有兼容模式参数
+ $_SERVER['PATH_INFO'] = $_GET[$varPath];
+ unset($_GET[$varPath]);
+ } elseif (IS_CLI) {
+ // CLI模式下 index.php module/controller/action/params/...
+ $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
+ }
+
+ // 开启子域名部署
+ if (C('APP_SUB_DOMAIN_DEPLOY')) {
+ $rules = C('APP_SUB_DOMAIN_RULES');
+ if (isset($rules[$_SERVER['HTTP_HOST']])) {
+ // 完整域名或者IP配置
+ define('APP_DOMAIN', $_SERVER['HTTP_HOST']); // 当前完整域名
+ $rule = $rules[APP_DOMAIN];
+ } else {
+ if (strpos(C('APP_DOMAIN_SUFFIX'), '.')) {
+ // com.cn net.cn
+ $domain = array_slice(explode('.', $_SERVER['HTTP_HOST']), 0, -3);
+ } else {
+ $domain = array_slice(explode('.', $_SERVER['HTTP_HOST']), 0, -2);
+ }
+ if (!empty($domain)) {
+ $subDomain = implode('.', $domain);
+ define('SUB_DOMAIN', $subDomain); // 当前完整子域名
+ $domain2 = array_pop($domain); // 二级域名
+ if ($domain) {
+ // 存在三级域名
+ $domain3 = array_pop($domain);
+ }
+ if (isset($rules[$subDomain])) {
+ // 子域名
+ $rule = $rules[$subDomain];
+ } elseif (isset($rules['*.' . $domain2]) && !empty($domain3)) {
+ // 泛三级域名
+ $rule = $rules['*.' . $domain2];
+ $panDomain = $domain3;
+ } elseif (isset($rules['*']) && !empty($domain2) && 'www' != $domain2) {
+ // 泛二级域名
+ $rule = $rules['*'];
+ $panDomain = $domain2;
+ }
+ }
+ }
+
+ if (!empty($rule)) {
+ // 子域名部署规则 '子域名'=>array('模块名[/控制器名]','var1=a&var2=b');
+ if (is_array($rule)) {
+ list($rule, $vars) = $rule;
+ }
+ $array = explode('/', $rule);
+ // 模块绑定
+ define('BIND_MODULE', array_shift($array));
+ // 控制器绑定
+ if (!empty($array)) {
+ $controller = array_shift($array);
+ if ($controller) {
+ define('BIND_CONTROLLER', $controller);
+ }
+ }
+ if (isset($vars)) {
+ // 传入参数
+ parse_str($vars, $parms);
+ if (isset($panDomain)) {
+ $pos = array_search('*', $parms);
+ if (false !== $pos) {
+ // 泛域名作为参数
+ $parms[$pos] = $panDomain;
+ }
+ }
+ $_GET = array_merge($_GET, $parms);
+ }
+ }
+ }
+ // 分析PATHINFO信息
+ if (!isset($_SERVER['PATH_INFO'])) {
+ $types = explode(',', C('URL_PATHINFO_FETCH'));
+ foreach ($types as $type) {
+ if (!empty($_SERVER[$type])) {
+ $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ?
+ substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type];
+ break;
+ }
+ }
+ }
+ if (empty($_SERVER['PATH_INFO'])) {
+ $_SERVER['PATH_INFO'] = '';
+ }
+ $depr = C('URL_PATHINFO_DEPR');
+ define('MODULE_PATHINFO_DEPR', $depr);
+ define('__INFO__', trim($_SERVER['PATH_INFO'], '/'));
+ // URL后缀
+ define('__EXT__', strtolower(pathinfo($_SERVER['PATH_INFO'], PATHINFO_EXTENSION)));
+
+ $_SERVER['PATH_INFO'] = __INFO__;
+
+ if (__INFO__ && C('MULTI_MODULE') && !defined('BIND_MODULE')) {
+ // 获取模块名
+ $paths = explode($depr, __INFO__, 2);
+ $allowList = C('MODULE_ALLOW_LIST'); // 允许的模块列表
+ $module = preg_replace('/\.' . __EXT__ . '$/i', '', $paths[0]);
+ if (empty($allowList) || (is_array($allowList) && in_array_case($module, $allowList))) {
+ $_GET[$varModule] = $module;
+ $_SERVER['PATH_INFO'] = isset($paths[1]) ? $paths[1] : '';
+ }
+ }
+
+ // 获取模块名称
+ define('MODULE_NAME', defined('BIND_MODULE') ? BIND_MODULE : self::getModule($varModule));
+
+ // 检测模块是否存在
+ if (MODULE_NAME && (defined('BIND_MODULE') || !in_array_case(MODULE_NAME, C('MODULE_DENY_LIST'))) && is_dir(APP_PATH . MODULE_NAME)) {
+ // 定义当前模块路径
+ define('MODULE_PATH', APP_PATH . MODULE_NAME . '/');
+ // 定义当前模块的模版缓存路径
+ C('CACHE_PATH', CACHE_PATH . MODULE_NAME . '/');
+
+ // 加载模块配置文件
+ if (is_file(MODULE_PATH . 'Conf/config.php')) {
+ C(include MODULE_PATH . 'Conf/config.php');
+ }
+
+ // 加载模块别名定义
+ if (is_file(MODULE_PATH . 'Conf/alias.php')) {
+ Think::addMap(include MODULE_PATH . 'Conf/alias.php');
+ }
+
+ // 加载模块函数文件
+ if (is_file(MODULE_PATH . 'Common/function.php')) {
+ include MODULE_PATH . 'Common/function.php';
+ }
+
+ } else {
+ E(L('_MODULE_NOT_EXIST_') . ':' . MODULE_NAME);
+ }
+
+ if ('' != $_SERVER['PATH_INFO'] && (!C('URL_ROUTER_ON') || !Route::check())) {
+ // 检测路由规则 如果没有则按默认规则调度URL
+ // 检查禁止访问的URL后缀
+ if (C('URL_DENY_SUFFIX') && preg_match('/\.(' . trim(C('URL_DENY_SUFFIX'), '.') . ')$/i', $_SERVER['PATH_INFO'])) {
+ send_http_status(404);
+ exit;
+ }
+
+ // 去除URL后缀
+ $_SERVER['PATH_INFO'] = preg_replace(C('URL_HTML_SUFFIX') ? '/\.(' . trim(C('URL_HTML_SUFFIX'), '.') . ')$/i' : '/\.' . __EXT__ . '$/i', '', $_SERVER['PATH_INFO']);
+
+ $depr = C('URL_PATHINFO_DEPR');
+ $paths = explode($depr, trim($_SERVER['PATH_INFO'], $depr));
+
+ if (!defined('BIND_CONTROLLER')) {
+// 获取控制器
+ $_GET[$varController] = array_shift($paths);
+ }
+ // 获取操作
+ if (!defined('BIND_ACTION')) {
+ $_GET[$varAction] = array_shift($paths);
+ }
+ // 解析剩余的URL参数
+ $var = array();
+ if (C('URL_PARAMS_BIND') && 1 == C('URL_PARAMS_BIND_TYPE')) {
+ // URL参数按顺序绑定变量
+ $var = $paths;
+ } else {
+ preg_replace_callback('/(\w+)\/([^\/]+)/', function ($match) use (&$var) {$var[$match[1]] = strip_tags($match[2]);}, implode('/', $paths));
+ }
+ $_GET = array_merge($var, $_GET);
+ }
+ // 获取控制器和操作名
+ define('CONTROLLER_NAME', defined('BIND_CONTROLLER') ? BIND_CONTROLLER : self::getController($varController, $urlCase));
+ define('ACTION_NAME', defined('BIND_ACTION') ? BIND_ACTION : self::getAction($varAction, $urlCase));
+ //保证$_REQUEST正常取值
+ $_REQUEST = array_merge($_POST, $_GET);
+ }
+
+ /**
+ * 获得实际的控制器名称
+ */
+ private static function getController($var, $urlCase)
+ {
+ $controller = (!empty($_GET[$var]) ? $_GET[$var] : C('DEFAULT_CONTROLLER'));
+ unset($_GET[$var]);
+ if ($urlCase) {
+ // URL地址不区分大小写
+ // 智能识别方式 user_type 识别到 UserTypeController 控制器
+ $controller = parse_name($controller, 1);
+ }
+ return strip_tags(ucfirst($controller));
+ }
+
+ /**
+ * 获得实际的操作名称
+ */
+ private static function getAction($var, $urlCase)
+ {
+ $action = !empty($_POST[$var]) ?
+ $_POST[$var] :
+ (!empty($_GET[$var]) ? $_GET[$var] : C('DEFAULT_ACTION'));
+ unset($_POST[$var], $_GET[$var]);
+ return strip_tags($urlCase ? strtolower($action) : $action);
+ }
+
+ /**
+ * 获得实际的模块名称
+ */
+ private static function getModule($var)
+ {
+ $module = (!empty($_GET[$var]) ? $_GET[$var] : C('DEFAULT_MODULE'));
+ unset($_GET[$var]);
+ if ($maps = C('URL_MODULE_MAP')) {
+ if (isset($maps[strtolower($module)])) {
+ // 记录当前别名
+ define('MODULE_ALIAS', strtolower($module));
+ // 获取实际的模块名
+ return ucfirst($maps[MODULE_ALIAS]);
+ } elseif (array_search(strtolower($module), $maps)) {
+ // 禁止访问原始模块
+ return '';
+ }
+ }
+ return strip_tags(ucfirst(strtolower($module)));
+ }
+
+}
diff --git a/Framework/Mode/Api/functions.php b/Framework/Mode/Api/functions.php
new file mode 100644
index 00000000..7350f876
--- /dev/null
+++ b/Framework/Mode/Api/functions.php
@@ -0,0 +1,1290 @@
+
+// +----------------------------------------------------------------------
+
+/**
+ * Think API模式函数库
+ */
+
+/**
+ * 获取和设置配置参数 支持批量定义
+ * @param string|array $name 配置变量
+ * @param mixed $value 配置值
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+function C($name = null, $value = null, $default = null)
+{
+ static $_config = array();
+ // 无参数时获取所有
+ if (empty($name)) {
+ return $_config;
+ }
+ // 优先执行设置获取或赋值
+ if (is_string($name)) {
+ if (!strpos($name, '.')) {
+ $name = strtolower($name);
+ if (is_null($value)) {
+ return isset($_config[$name]) ? $_config[$name] : $default;
+ }
+
+ $_config[$name] = $value;
+ return;
+ }
+ // 二维数组设置和获取支持
+ $name = explode('.', $name);
+ $name[0] = strtolower($name[0]);
+ if (is_null($value)) {
+ return isset($_config[$name[0]][$name[1]]) ? $_config[$name[0]][$name[1]] : $default;
+ }
+
+ $_config[$name[0]][$name[1]] = $value;
+ return;
+ }
+ // 批量设置
+ if (is_array($name)) {
+ $_config = array_merge($_config, array_change_key_case($name));
+ return;
+ }
+ return null; // 避免非法参数
+}
+
+/**
+ * 加载配置文件 支持格式转换 仅支持一级配置
+ * @param string $file 配置文件名
+ * @param string $parse 配置解析方法 有些格式需要用户自己解析
+ * @return void
+ */
+function load_config($file, $parse = CONF_PARSE)
+{
+ $ext = pathinfo($file, PATHINFO_EXTENSION);
+ switch ($ext) {
+ case 'php':
+ return include $file;
+ case 'ini':
+ return parse_ini_file($file);
+ case 'yaml':
+ return yaml_parse_file($file);
+ case 'xml':
+ return (array) simplexml_load_file($file);
+ case 'json':
+ return json_decode(file_get_contents($file), true);
+ default:
+ if (function_exists($parse)) {
+ return $parse($file);
+ } else {
+ E(L('_NOT_SUPPORT_') . ':' . $ext);
+ }
+ }
+}
+
+/**
+ * 抛出异常处理
+ * @param string $msg 异常消息
+ * @param integer $code 异常代码 默认为0
+ * @return void
+ */
+function E($msg, $code = 0)
+{
+ throw new Think\Exception($msg, $code);
+}
+
+/**
+ * 记录和统计时间(微秒)和内存使用情况
+ * 使用方法:
+ *
+ * G('begin'); // 记录开始标记位
+ * // ... 区间运行代码
+ * G('end'); // 记录结束标签位
+ * echo G('begin','end',6); // 统计区间运行时间 精确到小数后6位
+ * echo G('begin','end','m'); // 统计区间内存使用情况
+ * 如果end标记位没有定义,则会自动以当前作为标记位
+ * 其中统计内存使用需要 MEMORY_LIMIT_ON 常量为true才有效
+ *
+ * @param string $start 开始标签
+ * @param string $end 结束标签
+ * @param integer|string $dec 小数位或者m
+ * @return mixed
+ */
+function G($start, $end = '', $dec = 4)
+{
+ static $_info = array();
+ static $_mem = array();
+ if (is_float($end)) {
+ // 记录时间
+ $_info[$start] = $end;
+ } elseif (!empty($end)) {
+ // 统计时间和内存使用
+ if (!isset($_info[$end])) {
+ $_info[$end] = microtime(true);
+ }
+
+ if (MEMORY_LIMIT_ON && 'm' == $dec) {
+ if (!isset($_mem[$end])) {
+ $_mem[$end] = memory_get_usage();
+ }
+
+ return number_format(($_mem[$end] - $_mem[$start]) / 1024);
+ } else {
+ return number_format(($_info[$end] - $_info[$start]), $dec);
+ }
+
+ } else {
+ // 记录时间和内存使用
+ $_info[$start] = microtime(true);
+ if (MEMORY_LIMIT_ON) {
+ $_mem[$start] = memory_get_usage();
+ }
+
+ }
+}
+
+/**
+ * 获取和设置语言定义(不区分大小写)
+ * @param string|array $name 语言变量
+ * @param string $value 语言值
+ * @return mixed
+ */
+function L($name = null, $value = null)
+{
+ static $_lang = array();
+ // 空参数返回所有定义
+ if (empty($name)) {
+ return $_lang;
+ }
+
+ // 判断语言获取(或设置)
+ // 若不存在,直接返回全大写$name
+ if (is_string($name)) {
+ $name = strtoupper($name);
+ if (is_null($value)) {
+ return isset($_lang[$name]) ? $_lang[$name] : $name;
+ }
+
+ $_lang[$name] = $value; // 语言定义
+ return;
+ }
+ // 批量定义
+ if (is_array($name)) {
+ $_lang = array_merge($_lang, array_change_key_case($name, CASE_UPPER));
+ }
+
+ return;
+}
+
+/**
+ * 添加和获取页面Trace记录
+ * @param string $value 变量
+ * @param string $label 标签
+ * @param string $level 日志级别
+ * @param boolean $record 是否记录日志
+ * @return void
+ */
+function trace($value = '[think]', $label = '', $level = 'DEBUG', $record = false)
+{
+ return Think\Think::trace($value, $label, $level, $record);
+}
+
+/**
+ * 编译文件
+ * @param string $filename 文件名
+ * @return string
+ */
+function compile($filename)
+{
+ $content = php_strip_whitespace($filename);
+ $content = trim(substr($content, 5));
+ // 替换预编译指令
+ $content = preg_replace('/\/\/\[RUNTIME\](.*?)\/\/\[\/RUNTIME\]/s', '', $content);
+ if (0 === strpos($content, 'namespace')) {
+ $content = preg_replace('/namespace\s(.*?);/', 'namespace \\1{', $content, 1);
+ } else {
+ $content = 'namespace {' . $content;
+ }
+ if ('?>' == substr($content, -2)) {
+ $content = substr($content, 0, -2);
+ }
+
+ return $content . '}';
+}
+
+/**
+ * 获取输入参数 支持过滤和默认值
+ * 使用方法:
+ *
+ * I('id',0); 获取id参数 自动判断get或者post
+ * I('post.name','','htmlspecialchars'); 获取$_POST['name']
+ * I('get.'); 获取$_GET
+ *
+ * @param string $name 变量的名称 支持指定类型
+ * @param mixed $default 不存在的时候默认值
+ * @param mixed $filter 参数过滤方法
+ * @param mixed $datas 要获取的额外数据源
+ * @return mixed
+ */
+function I($name, $default = '', $filter = null, $datas = null)
+{
+ if (strpos($name, '/')) {
+ // 指定修饰符
+ list($name, $type) = explode('/', $name, 2);
+ }
+ if (strpos($name, '.')) {
+ // 指定参数来源
+ list($method, $name) = explode('.', $name, 2);
+ } else {
+ // 默认为自动判断
+ $method = 'param';
+ }
+ switch (strtolower($method)) {
+ case 'get':$input = &$_GET;
+ break;
+ case 'post':$input = &$_POST;
+ break;
+ case 'put':parse_str(file_get_contents('php://input'), $input);
+ break;
+ case 'param':
+ switch ($_SERVER['REQUEST_METHOD']) {
+ case 'POST':
+ $input = $_POST;
+ break;
+ case 'PUT':
+ parse_str(file_get_contents('php://input'), $input);
+ break;
+ default:
+ $input = $_GET;
+ }
+ break;
+ case 'path':
+ $input = array();
+ if (!empty($_SERVER['PATH_INFO'])) {
+ $depr = C('URL_PATHINFO_DEPR');
+ $input = explode($depr, trim($_SERVER['PATH_INFO'], $depr));
+ }
+ break;
+ case 'request':$input = &$_REQUEST;
+ break;
+ case 'session':$input = &$_SESSION;
+ break;
+ case 'cookie':$input = &$_COOKIE;
+ break;
+ case 'server':$input = &$_SERVER;
+ break;
+ case 'globals':$input = &$GLOBALS;
+ break;
+ case 'data':$input = &$datas;
+ break;
+ default:
+ return null;
+ }
+ if ('' == $name) {
+ // 获取全部变量
+ $data = $input;
+ $filters = isset($filter) ? $filter : C('DEFAULT_FILTER');
+ if ($filters) {
+ if (is_string($filters)) {
+ $filters = explode(',', $filters);
+ }
+ foreach ($filters as $filter) {
+ $data = arrayMapRecursive($filter, $data); // 参数过滤
+ }
+ }
+ } elseif (isset($input[$name])) {
+ // 取值操作
+ $data = $input[$name];
+ $filters = isset($filter) ? $filter : C('DEFAULT_FILTER');
+ if ($filters) {
+ if (is_string($filters)) {
+ $filters = explode(',', $filters);
+ } elseif (is_int($filters)) {
+ $filters = array($filters);
+ }
+
+ foreach ($filters as $filter) {
+ if (function_exists($filter)) {
+ $data = is_array($data) ? arrayMapRecursive($filter, $data) : $filter($data); // 参数过滤
+ } elseif (0 === strpos($filter, '/')) {
+ // 支持正则验证
+ if (1 !== preg_match($filter, (string) $data)) {
+ return isset($default) ? $default : null;
+ }
+ } else {
+ $data = filter_var($data, is_int($filter) ? $filter : filter_id($filter));
+ if (false === $data) {
+ return isset($default) ? $default : null;
+ }
+ }
+ }
+ }
+ if (!empty($type)) {
+ switch (strtolower($type)) {
+ case 's': // 字符串
+ $data = (string) $data;
+ break;
+ case 'a': // 数组
+ $data = (array) $data;
+ break;
+ case 'd': // 数字
+ $data = (int) $data;
+ break;
+ case 'f': // 浮点
+ $data = (float) $data;
+ break;
+ case 'b': // 布尔
+ $data = (boolean) $data;
+ break;
+ }
+ }
+ } else {
+ // 变量默认值
+ $data = isset($default) ? $default : null;
+ }
+ is_array($data) && array_walk_recursive($data, 'think_filter');
+ return $data;
+}
+
+function array_map_recursive($filter, $data)
+{
+ $result = array();
+ foreach ($data as $key => $val) {
+ $result[$key] = is_array($val)
+ ? array_map_recursive($filter, $val)
+ : call_user_func($filter, $val);
+ }
+ return $result;
+}
+
+/**
+ * 设置和获取统计数据
+ * 使用方法:
+ *
+ * N('db',1); // 记录数据库操作次数
+ * N('read',1); // 记录读取次数
+ * echo N('db'); // 获取当前页面数据库的所有操作次数
+ * echo N('read'); // 获取当前页面读取次数
+ *
+ * @param string $key 标识位置
+ * @param integer $step 步进值
+ * @return mixed
+ */
+function N($key, $step = 0, $save = false)
+{
+ static $_num = array();
+ if (!isset($_num[$key])) {
+ $_num[$key] = (false !== $save) ? S('N_' . $key) : 0;
+ }
+ if (empty($step)) {
+ return $_num[$key];
+ } else {
+ $_num[$key] = $_num[$key] + (int) $step;
+ }
+
+ if (false !== $save) {
+ // 保存结果
+ S('N_' . $key, $_num[$key], $save);
+ }
+}
+
+/**
+ * 字符串命名风格转换
+ * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
+ * @param string $name 字符串
+ * @param integer $type 转换类型
+ * @return string
+ */
+function parse_name($name, $type = 0)
+{
+ if ($type) {
+ return ucfirst(preg_replace_callback('/_([a-zA-Z])/', function ($match) {return strtoupper($match[1]);}, $name));
+ } else {
+ return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
+ }
+}
+
+/**
+ * 优化的require_once
+ * @param string $filename 文件地址
+ * @return boolean
+ */
+function require_cache($filename)
+{
+ static $_importFiles = array();
+ if (!isset($_importFiles[$filename])) {
+ if (file_exists_case($filename)) {
+ require $filename;
+ $_importFiles[$filename] = true;
+ } else {
+ $_importFiles[$filename] = false;
+ }
+ }
+ return $_importFiles[$filename];
+}
+
+/**
+ * 区分大小写的文件存在判断
+ * @param string $filename 文件地址
+ * @return boolean
+ */
+function file_exists_case($filename)
+{
+ if (is_file($filename)) {
+ if (IS_WIN && APP_DEBUG) {
+ if (basename(realpath($filename)) != basename($filename)) {
+ return false;
+ }
+
+ }
+ return true;
+ }
+ return false;
+}
+
+/**
+ * 导入所需的类库 同java的Import 本函数有缓存功能
+ * @param string $class 类库命名空间字符串
+ * @param string $baseUrl 起始路径
+ * @param string $ext 导入的文件扩展名
+ * @return boolean
+ */
+function import($class, $baseUrl = '', $ext = EXT)
+{
+ static $_file = array();
+ $class = str_replace(array('.', '#'), array('/', '.'), $class);
+ if (isset($_file[$class . $baseUrl])) {
+ return true;
+ } else {
+ $_file[$class . $baseUrl] = true;
+ }
+
+ $class_strut = explode('/', $class);
+ if (empty($baseUrl)) {
+ if ('@' == $class_strut[0] || MODULE_NAME == $class_strut[0]) {
+ //加载当前模块的类库
+ $baseUrl = MODULE_PATH;
+ $class = substr_replace($class, '', 0, strlen($class_strut[0]) + 1);
+ } elseif (in_array($class_strut[0], array('Think', 'Org', 'Behavior', 'Com', 'Vendor')) || is_dir(LIB_PATH . $class_strut[0])) {
+ // 系统类库包和第三方类库包
+ $baseUrl = LIB_PATH;
+ } else {
+ // 加载其他模块的类库
+ $baseUrl = APP_PATH;
+ }
+ }
+ if (substr($baseUrl, -1) != '/') {
+ $baseUrl .= '/';
+ }
+
+ $classfile = $baseUrl . $class . $ext;
+ if (!class_exists(basename($class), false)) {
+ // 如果类不存在 则导入类库文件
+ return require_cache($classfile);
+ }
+}
+
+/**
+ * 基于命名空间方式导入函数库
+ * load('@.Util.Array')
+ * @param string $name 函数库命名空间字符串
+ * @param string $baseUrl 起始路径
+ * @param string $ext 导入的文件扩展名
+ * @return void
+ */
+function load($name, $baseUrl = '', $ext = '.php')
+{
+ $name = str_replace(array('.', '#'), array('/', '.'), $name);
+ if (empty($baseUrl)) {
+ if (0 === strpos($name, '@/')) {
+ //加载当前模块函数库
+ $baseUrl = MODULE_PATH . 'Common/';
+ $name = substr($name, 2);
+ } else {
+ //加载其他模块函数库
+ $array = explode('/', $name);
+ $baseUrl = APP_PATH . array_shift($array) . '/Common/';
+ $name = implode('/', $array);
+ }
+ }
+ if (substr($baseUrl, -1) != '/') {
+ $baseUrl .= '/';
+ }
+
+ require_cache($baseUrl . $name . $ext);
+}
+
+/**
+ * 快速导入第三方框架类库 所有第三方框架的类库文件统一放到 系统的Vendor目录下面
+ * @param string $class 类库
+ * @param string $baseUrl 基础目录
+ * @param string $ext 类库后缀
+ * @return boolean
+ */
+function vendor($class, $baseUrl = '', $ext = '.php')
+{
+ if (empty($baseUrl)) {
+ $baseUrl = VENDOR_PATH;
+ }
+
+ return import($class, $baseUrl, $ext);
+}
+
+/**
+ * D函数用于实例化模型类 格式 [资源://][模块/]模型
+ * @param string $name 资源地址
+ * @param string $layer 模型层名称
+ * @return Model
+ */
+function D($name = '', $layer = '')
+{
+ if (empty($name)) {
+ return new Think\Model;
+ }
+
+ static $_model = array();
+ $layer = $layer ?: C('DEFAULT_M_LAYER');
+ if (isset($_model[$name . $layer])) {
+ return $_model[$name . $layer];
+ }
+
+ $class = parse_res_name($name, $layer);
+ if (class_exists($class)) {
+ $model = new $class(basename($name));
+ } elseif (false === strpos($name, '/')) {
+ // 自动加载公共模块下面的模型
+ $class = '\\Common\\' . $layer . '\\' . $name . $layer;
+ $model = class_exists($class) ? new $class($name) : new Think\Model($name);
+ } else {
+ Think\Log::record('D方法实例化没找到模型类' . $class, Think\Log::NOTICE);
+ $model = new Think\Model(basename($name));
+ }
+ $_model[$name . $layer] = $model;
+ return $model;
+}
+
+/**
+ * M函数用于实例化一个没有模型文件的Model
+ * @param string $name Model名称 支持指定基础模型 例如 MongoModel:User
+ * @param string $tablePrefix 表前缀
+ * @param mixed $connection 数据库连接信息
+ * @return Model
+ */
+function M($name = '', $tablePrefix = '', $connection = '')
+{
+ static $_model = array();
+ if (strpos($name, ':')) {
+ list($class, $name) = explode(':', $name);
+ } else {
+ $class = 'Think\\Model';
+ }
+ $guid = (is_array($connection) ? implode('', $connection) : $connection) . $tablePrefix . $name . '_' . $class;
+ if (!isset($_model[$guid])) {
+ $_model[$guid] = new $class($name, $tablePrefix, $connection);
+ }
+
+ return $_model[$guid];
+}
+
+/**
+ * 解析资源地址并导入类库文件
+ * 例如 module/controller addon://module/behavior
+ * @param string $name 资源地址 格式:[扩展://][模块/]资源名
+ * @param string $layer 分层名称
+ * @return string
+ */
+function parse_res_name($name, $layer, $level = 1)
+{
+ if (strpos($name, '://')) {
+// 指定扩展资源
+ list($extend, $name) = explode('://', $name);
+ } else {
+ $extend = '';
+ }
+ if (strpos($name, '/') && substr_count($name, '/') >= $level) {
+ // 指定模块
+ list($module, $name) = explode('/', $name, 2);
+ } else {
+ $module = MODULE_NAME;
+ }
+ $array = explode('/', $name);
+ $class = $module . '\\' . $layer;
+ foreach ($array as $name) {
+ $class .= '\\' . parse_name($name, 1);
+ }
+ // 导入资源类库
+ if ($extend) {
+ // 扩展资源
+ $class = $extend . '\\' . $class;
+ }
+ return $class . $layer;
+}
+
+/**
+ * A函数用于实例化控制器 格式:[资源://][模块/]控制器
+ * @param string $name 资源地址
+ * @param string $layer 控制层名称
+ * @param integer $level 控制器层次
+ * @return Controller|false
+ */
+function A($name, $layer = '', $level = '')
+{
+ static $_action = array();
+ $layer = $layer ?: C('DEFAULT_C_LAYER');
+ $level = $level ?: (C('DEFAULT_C_LAYER') == $layer ? C('CONTROLLER_LEVEL') : 1);
+ if (isset($_action[$name . $layer])) {
+ return $_action[$name . $layer];
+ }
+
+ $class = parse_res_name($name, $layer, $level);
+ if (class_exists($class)) {
+ $action = new $class();
+ $_action[$name . $layer] = $action;
+ return $action;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * 远程调用控制器的操作方法 URL 参数格式 [资源://][模块/]控制器/操作
+ * @param string $url 调用地址
+ * @param string|array $vars 调用参数 支持字符串和数组
+ * @param string $layer 要调用的控制层名称
+ * @return mixed
+ */
+function R($url, $vars = array(), $layer = '')
+{
+ $info = pathinfo($url);
+ $action = $info['basename'];
+ $module = $info['dirname'];
+ $class = A($module, $layer);
+ if ($class) {
+ if (is_string($vars)) {
+ parse_str($vars, $vars);
+ }
+ return call_user_func_array(array(&$class, $action . C('ACTION_SUFFIX')), $vars);
+ } else {
+ return false;
+ }
+}
+
+/**
+ * 执行某个行为
+ * @param string $name 行为名称
+ * @param Mixed $params 传入的参数
+ * @return void
+ */
+function B($name, &$params = null)
+{
+ if (strpos($name, '/')) {
+ list($name, $tag) = explode('/', $name);
+ } else {
+ $tag = 'run';
+ }
+ return \Think\Hook::exec($name, $tag, $params);
+}
+
+/**
+ * 去除代码中的空白和注释
+ * @param string $content 代码内容
+ * @return string
+ */
+function strip_whitespace($content)
+{
+ $stripStr = '';
+ //分析php源码
+ $tokens = token_get_all($content);
+ $last_space = false;
+ for ($i = 0, $j = count($tokens); $i < $j; $i++) {
+ if (is_string($tokens[$i])) {
+ $last_space = false;
+ $stripStr .= $tokens[$i];
+ } else {
+ switch ($tokens[$i][0]) {
+ //过滤各种PHP注释
+ case T_COMMENT:
+ case T_DOC_COMMENT:
+ break;
+ //过滤空格
+ case T_WHITESPACE:
+ if (!$last_space) {
+ $stripStr .= ' ';
+ $last_space = true;
+ }
+ break;
+ case T_START_HEREDOC:
+ $stripStr .= "<<' . $label . htmlspecialchars($output, ENT_QUOTES) . '';
+ } else {
+ $output = $label . print_r($var, true);
+ }
+ } else {
+ ob_start();
+ var_dump($var);
+ $output = ob_get_clean();
+ if (!extension_loaded('xdebug')) {
+ $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output);
+ $output = '' . $label . htmlspecialchars($output, ENT_QUOTES) . ' ';
+ }
+ }
+ if ($echo) {
+ echo ($output);
+ return null;
+ } else {
+ return $output;
+ }
+
+}
+
+/**
+ * URL重定向
+ * @param string $url 重定向的URL地址
+ * @param integer $time 重定向的等待时间(秒)
+ * @param string $msg 重定向前的提示信息
+ * @return void
+ */
+function redirect($url, $time = 0, $msg = '')
+{
+ //多行URL地址支持
+ $url = str_replace(array("\n", "\r"), '', $url);
+ if (empty($msg)) {
+ $msg = "系统将在{$time}秒之后自动跳转到{$url}!";
+ }
+
+ if (!headers_sent()) {
+ // redirect
+ if (0 === $time) {
+ header('Location: ' . $url);
+ } else {
+ header("refresh:{$time};url={$url}");
+ echo ($msg);
+ }
+ exit();
+ } else {
+ $str = " ";
+ if (0 != $time) {
+ $str .= $msg;
+ }
+
+ exit($str);
+ }
+}
+
+/**
+ * 缓存管理
+ * @param mixed $name 缓存名称,如果为数组表示进行缓存设置
+ * @param mixed $value 缓存值
+ * @param mixed $options 缓存参数
+ * @return mixed
+ */
+function S($name, $value = '', $options = null)
+{
+ static $cache = '';
+ if (is_array($options) && empty($cache)) {
+ // 缓存操作的同时初始化
+ $type = isset($options['type']) ? $options['type'] : '';
+ $cache = Think\Cache::getInstance($type, $options);
+ } elseif (is_array($name)) {
+ // 缓存初始化
+ $type = isset($name['type']) ? $name['type'] : '';
+ $cache = Think\Cache::getInstance($type, $name);
+ return $cache;
+ } elseif (empty($cache)) {
+ // 自动初始化
+ $cache = Think\Cache::getInstance();
+ }
+ if ('' === $value) {
+ // 获取缓存
+ return $cache->get($name);
+ } elseif (is_null($value)) {
+ // 删除缓存
+ return $cache->rm($name);
+ } else {
+ // 缓存数据
+ if (is_array($options)) {
+ $expire = isset($options['expire']) ? $options['expire'] : null;
+ } else {
+ $expire = is_numeric($options) ? $options : null;
+ }
+ return $cache->set($name, $value, $expire);
+ }
+}
+
+/**
+ * 快速文件数据读取和保存 针对简单类型数据 字符串、数组
+ * @param string $name 缓存名称
+ * @param mixed $value 缓存值
+ * @param string $path 缓存路径
+ * @return mixed
+ */
+function F($name, $value = '', $path = DATA_PATH)
+{
+ static $_cache = array();
+ $filename = $path . $name . '.php';
+ if ('' !== $value) {
+ if (is_null($value)) {
+ // 删除缓存
+ if (false !== strpos($name, '*')) {
+ return false; // TODO
+ } else {
+ unset($_cache[$name]);
+ return Think\Storage::unlink($filename, 'F');
+ }
+ } else {
+ Think\Storage::put($filename, serialize($value), 'F');
+ // 缓存数据
+ $_cache[$name] = $value;
+ return;
+ }
+ }
+ // 获取缓存数据
+ if (isset($_cache[$name])) {
+ return $_cache[$name];
+ }
+
+ if (Think\Storage::has($filename, 'F')) {
+ $value = unserialize(Think\Storage::read($filename, 'F'));
+ $_cache[$name] = $value;
+ } else {
+ $value = false;
+ }
+ return $value;
+}
+
+/**
+ * 根据PHP各种类型变量生成唯一标识号
+ * @param mixed $mix 变量
+ * @return string
+ */
+function to_guid_string($mix)
+{
+ if (is_object($mix)) {
+ return spl_object_hash($mix);
+ } elseif (is_resource($mix)) {
+ $mix = get_resource_type($mix) . strval($mix);
+ } else {
+ $mix = serialize($mix);
+ }
+ return md5($mix);
+}
+
+/**
+ * XML编码
+ * @param mixed $data 数据
+ * @param string $root 根节点名
+ * @param string $item 数字索引的子节点名
+ * @param string $attr 根节点属性
+ * @param string $id 数字索引子节点key转换的属性名
+ * @param string $encoding 数据编码
+ * @return string
+ */
+function xml_encode($data, $root = 'think', $item = 'item', $attr = '', $id = 'id', $encoding = 'utf-8')
+{
+ if (is_array($attr)) {
+ $_attr = array();
+ foreach ($attr as $key => $value) {
+ $_attr[] = "{$key}=\"{$value}\"";
+ }
+ $attr = implode(' ', $_attr);
+ }
+ $attr = trim($attr);
+ $attr = empty($attr) ? '' : " {$attr}";
+ $xml = "";
+ $xml .= "<{$root}{$attr}>";
+ $xml .= data_to_xml($data, $item, $id);
+ $xml .= "{$root}>";
+ return $xml;
+}
+
+/**
+ * 数据XML编码
+ * @param mixed $data 数据
+ * @param string $item 数字索引时的节点名称
+ * @param string $id 数字索引key转换为的属性名
+ * @return string
+ */
+function data_to_xml($data, $item = 'item', $id = 'id')
+{
+ $xml = $attr = '';
+ foreach ($data as $key => $val) {
+ if (is_numeric($key)) {
+ $id && $attr = " {$id}=\"{$key}\"";
+ $key = $item;
+ }
+ $xml .= "<{$key}{$attr}>";
+ $xml .= (is_array($val) || is_object($val)) ? data_to_xml($val, $item, $id) : $val;
+ $xml .= "{$key}>";
+ }
+ return $xml;
+}
+
+/**
+ * session管理函数
+ * @param string|array $name session名称 如果为数组则表示进行session设置
+ * @param mixed $value session值
+ * @return mixed
+ */
+function session($name, $value = '')
+{
+ $prefix = C('SESSION_PREFIX');
+ if (is_array($name)) {
+ // session初始化 在session_start 之前调用
+ if (isset($name['prefix'])) {
+ C('SESSION_PREFIX', $name['prefix']);
+ }
+
+ if (C('VAR_SESSION_ID') && isset($_REQUEST[C('VAR_SESSION_ID')])) {
+ session_id($_REQUEST[C('VAR_SESSION_ID')]);
+ } elseif (isset($name['id'])) {
+ session_id($name['id']);
+ }
+ if ('common' != APP_MODE) {
+ // 其它模式可能不支持
+ ini_set('session.auto_start', 0);
+ }
+ if (isset($name['name'])) {
+ session_name($name['name']);
+ }
+
+ if (isset($name['path'])) {
+ session_save_path($name['path']);
+ }
+
+ if (isset($name['domain'])) {
+ ini_set('session.cookie_domain', $name['domain']);
+ }
+
+ if (isset($name['expire'])) {
+ ini_set('session.gc_maxlifetime', $name['expire']);
+ }
+
+ if (isset($name['use_trans_sid'])) {
+ ini_set('session.use_trans_sid', $name['use_trans_sid'] ? 1 : 0);
+ }
+
+ if (isset($name['use_cookies'])) {
+ ini_set('session.use_cookies', $name['use_cookies'] ? 1 : 0);
+ }
+
+ if (isset($name['cache_limiter'])) {
+ session_cache_limiter($name['cache_limiter']);
+ }
+
+ if (isset($name['cache_expire'])) {
+ session_cache_expire($name['cache_expire']);
+ }
+
+ if (isset($name['type'])) {
+ C('SESSION_TYPE', $name['type']);
+ }
+
+ if (C('SESSION_TYPE')) {
+ // 读取session驱动
+ $type = C('SESSION_TYPE');
+ $class = strpos($type, '\\') ? $type : 'Think\\Session\\Driver\\' . ucwords(strtolower($type));
+ $hander = new $class();
+ session_set_save_handler(
+ array(&$hander, "open"),
+ array(&$hander, "close"),
+ array(&$hander, "read"),
+ array(&$hander, "write"),
+ array(&$hander, "destroy"),
+ array(&$hander, "gc"));
+ }
+ // 启动session
+ if (C('SESSION_AUTO_START')) {
+ session_start();
+ }
+
+ } elseif ('' === $value) {
+ if (0 === strpos($name, '[')) {
+ // session 操作
+ if ('[pause]' == $name) {
+ // 暂停session
+ session_write_close();
+ } elseif ('[start]' == $name) {
+ // 启动session
+ session_start();
+ } elseif ('[destroy]' == $name) {
+ // 销毁session
+ $_SESSION = array();
+ session_unset();
+ session_destroy();
+ } elseif ('[regenerate]' == $name) {
+ // 重新生成id
+ session_regenerate_id();
+ }
+ } elseif (0 === strpos($name, '?')) {
+ // 检查session
+ $name = substr($name, 1);
+ if (strpos($name, '.')) {
+ // 支持数组
+ list($name1, $name2) = explode('.', $name);
+ return $prefix ? isset($_SESSION[$prefix][$name1][$name2]) : isset($_SESSION[$name1][$name2]);
+ } else {
+ return $prefix ? isset($_SESSION[$prefix][$name]) : isset($_SESSION[$name]);
+ }
+ } elseif (is_null($name)) {
+ // 清空session
+ if ($prefix) {
+ unset($_SESSION[$prefix]);
+ } else {
+ $_SESSION = array();
+ }
+ } elseif ($prefix) {
+ // 获取session
+ if (strpos($name, '.')) {
+ list($name1, $name2) = explode('.', $name);
+ return isset($_SESSION[$prefix][$name1][$name2]) ? $_SESSION[$prefix][$name1][$name2] : null;
+ } else {
+ return isset($_SESSION[$prefix][$name]) ? $_SESSION[$prefix][$name] : null;
+ }
+ } else {
+ if (strpos($name, '.')) {
+ list($name1, $name2) = explode('.', $name);
+ return isset($_SESSION[$name1][$name2]) ? $_SESSION[$name1][$name2] : null;
+ } else {
+ return isset($_SESSION[$name]) ? $_SESSION[$name] : null;
+ }
+ }
+ } elseif (is_null($value)) {
+ // 删除session
+ if ($prefix) {
+ unset($_SESSION[$prefix][$name]);
+ } else {
+ unset($_SESSION[$name]);
+ }
+ } else {
+ // 设置session
+ if ($prefix) {
+ if (!is_array($_SESSION[$prefix])) {
+ $_SESSION[$prefix] = array();
+ }
+ $_SESSION[$prefix][$name] = $value;
+ } else {
+ $_SESSION[$name] = $value;
+ }
+ }
+}
+
+/**
+ * Cookie 设置、获取、删除
+ * @param string $name cookie名称
+ * @param mixed $value cookie值
+ * @param mixed $options cookie参数
+ * @return mixed
+ */
+function cookie($name, $value = '', $option = null)
+{
+ // 默认设置
+ $config = array(
+ 'prefix' => C('COOKIE_PREFIX'), // cookie 名称前缀
+ 'expire' => C('COOKIE_EXPIRE'), // cookie 保存时间
+ 'path' => C('COOKIE_PATH'), // cookie 保存路径
+ 'domain' => C('COOKIE_DOMAIN'), // cookie 有效域名
+ );
+ // 参数设置(会覆盖黙认设置)
+ if (!is_null($option)) {
+ if (is_numeric($option)) {
+ $option = array('expire' => $option);
+ } elseif (is_string($option)) {
+ parse_str($option, $option);
+ }
+
+ $config = array_merge($config, array_change_key_case($option));
+ }
+ // 清除指定前缀的所有cookie
+ if (is_null($name)) {
+ if (empty($_COOKIE)) {
+ return;
+ }
+
+ // 要删除的cookie前缀,不指定则删除config设置的指定前缀
+ $prefix = empty($value) ? $config['prefix'] : $value;
+ if (!empty($prefix)) {
+ // 如果前缀为空字符串将不作处理直接返回
+ foreach ($_COOKIE as $key => $val) {
+ if (0 === stripos($key, $prefix)) {
+ setcookie($key, '', time() - 3600, $config['path'], $config['domain']);
+ unset($_COOKIE[$key]);
+ }
+ }
+ }
+ return;
+ }
+ $name = $config['prefix'] . $name;
+ if ('' === $value) {
+ if (isset($_COOKIE[$name])) {
+ $value = $_COOKIE[$name];
+ if (0 === strpos($value, 'think:')) {
+ $value = substr($value, 6);
+ return array_map('urldecode', json_decode(MAGIC_QUOTES_GPC ? stripslashes($value) : $value, true));
+ } else {
+ return $value;
+ }
+ } else {
+ return null;
+ }
+ } else {
+ if (is_null($value)) {
+ setcookie($name, '', time() - 3600, $config['path'], $config['domain']);
+ unset($_COOKIE[$name]); // 删除指定cookie
+ } else {
+ // 设置cookie
+ if (is_array($value)) {
+ $value = 'think:' . json_encode(array_map('urlencode', $value));
+ }
+ $expire = !empty($config['expire']) ? time() + intval($config['expire']) : 0;
+ setcookie($name, $value, $expire, $config['path'], $config['domain']);
+ $_COOKIE[$name] = $value;
+ }
+ }
+}
+
+/**
+ * 加载动态扩展文件
+ * @return void
+ */
+function load_ext_file($path)
+{
+ // 加载自定义外部文件
+ if (C('LOAD_EXT_FILE')) {
+ $files = explode(',', C('LOAD_EXT_FILE'));
+ foreach ($files as $file) {
+ $file = $path . 'Common/' . $file . '.php';
+ if (is_file($file)) {
+ include $file;
+ }
+
+ }
+ }
+ // 加载自定义的动态配置文件
+ if (C('LOAD_EXT_CONFIG')) {
+ $configs = C('LOAD_EXT_CONFIG');
+ if (is_string($configs)) {
+ $configs = explode(',', $configs);
+ }
+
+ foreach ($configs as $key => $config) {
+ $file = $path . 'Conf/' . $config . '.php';
+ if (is_file($file)) {
+ is_numeric($key) ? C(include $file) : C($key, include $file);
+ }
+ }
+ }
+}
+
+/**
+ * 获取客户端IP地址
+ * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字
+ * @return mixed
+ */
+function get_client_ip($type = 0)
+{
+ $type = $type ? 1 : 0;
+ static $ip = null;
+ if (null !== $ip) {
+ return $ip[$type];
+ }
+
+ if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+ $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
+ $pos = array_search('unknown', $arr);
+ if (false !== $pos) {
+ unset($arr[$pos]);
+ }
+
+ $ip = trim($arr[0]);
+ } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
+ $ip = $_SERVER['HTTP_CLIENT_IP'];
+ } elseif (isset($_SERVER['REMOTE_ADDR'])) {
+ $ip = $_SERVER['REMOTE_ADDR'];
+ }
+ // IP地址合法验证
+ $long = sprintf("%u", ip2long($ip));
+ $ip = $long ? array($ip, $long) : array('0.0.0.0', 0);
+ return $ip[$type];
+}
+
+/**
+ * 发送HTTP状态
+ * @param integer $code 状态码
+ * @return void
+ */
+function send_http_status($code)
+{
+ static $_status = array(
+ // Success 2xx
+ 200 => 'OK',
+ // Redirection 3xx
+ 301 => 'Moved Permanently',
+ 302 => 'Moved Temporarily ', // 1.1
+ // Client Error 4xx
+ 400 => 'Bad Request',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ // Server Error 5xx
+ 500 => 'Internal Server Error',
+ 503 => 'Service Unavailable',
+ );
+ if (isset($_status[$code])) {
+ header('HTTP/1.1 ' . $code . ' ' . $_status[$code]);
+ // 确保FastCGI模式下正常
+ header('Status:' . $code . ' ' . $_status[$code]);
+ }
+}
+
+// 不区分大小写的in_array实现
+function in_array_case($value, $array)
+{
+ return in_array(strtolower($value), array_map('strtolower', $array));
+}
+
+function think_filter(&$value)
+{
+ // TODO 其他安全过滤
+
+ // 过滤查询特殊字符
+ if (preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i', $value)) {
+ $value .= ' ';
+ }
+}
diff --git a/Framework/Mode/Lite/App.class.php b/Framework/Mode/Lite/App.class.php
new file mode 100644
index 00000000..8915d4e4
--- /dev/null
+++ b/Framework/Mode/Lite/App.class.php
@@ -0,0 +1,163 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * ThinkPHP 应用程序类 执行应用过程管理
+ */
+
+class App
+{
+
+ /**
+ * 应用程序初始化
+ * @access public
+ * @return void
+ */
+ public static function init()
+ {
+
+ // 日志目录转换为绝对路径 默认情况下存储到公共模块下面
+ C('LOG_PATH', realpath(LOG_PATH) . '/Common/');
+
+ // 定义当前请求的系统常量
+ define('NOW_TIME', $_SERVER['REQUEST_TIME']);
+ define('REQUEST_METHOD', $_SERVER['REQUEST_METHOD']);
+ define('IS_GET', REQUEST_METHOD == 'GET' ? true : false);
+ define('IS_POST', REQUEST_METHOD == 'POST' ? true : false);
+ define('IS_PUT', REQUEST_METHOD == 'PUT' ? true : false);
+ define('IS_DELETE', REQUEST_METHOD == 'DELETE' ? true : false);
+
+ // URL调度
+ Dispatcher::dispatch();
+
+ if (C('REQUEST_VARS_FILTER')) {
+ // 全局安全过滤
+ array_walk_recursive($_GET, 'think_filter');
+ array_walk_recursive($_POST, 'think_filter');
+ array_walk_recursive($_REQUEST, 'think_filter');
+ }
+
+ define('IS_AJAX', ((isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') || !empty($_POST[C('VAR_AJAX_SUBMIT')]) || !empty($_GET[C('VAR_AJAX_SUBMIT')])) ? true : false);
+
+ // TMPL_EXCEPTION_FILE 改为绝对地址
+ C('TMPL_EXCEPTION_FILE', realpath(C('TMPL_EXCEPTION_FILE')));
+ return;
+ }
+
+ /**
+ * 执行应用程序
+ * @access public
+ * @return void
+ */
+ public static function exec()
+ {
+
+ if (!preg_match('/^[A-Za-z](\/|\w)*$/', CONTROLLER_NAME)) {
+ // 安全检测
+ $module = false;
+ } else {
+ //创建控制器实例
+ $module = controller(CONTROLLER_NAME);
+ }
+
+ if (!$module) {
+ // 是否定义Empty控制器
+ $module = A('Empty');
+ if (!$module) {
+ E(L('_CONTROLLER_NOT_EXIST_') . ':' . CONTROLLER_NAME);
+ }
+ }
+
+ // 获取当前操作名 支持动态路由
+ $action = ACTION_NAME . C('ACTION_SUFFIX');
+
+ try {
+ if (!preg_match('/^[A-Za-z](\w)*$/', $action)) {
+ // 非法操作
+ throw new \ReflectionException();
+ }
+ //执行当前操作
+ $method = new \ReflectionMethod($module, $action);
+ if ($method->isPublic() && !$method->isStatic()) {
+ $class = new \ReflectionClass($module);
+ // URL参数绑定检测
+ if ($method->getNumberOfParameters() > 0 && C('URL_PARAMS_BIND')) {
+ switch ($_SERVER['REQUEST_METHOD']) {
+ case 'POST':
+ $vars = array_merge($_GET, $_POST);
+ break;
+ case 'PUT':
+ parse_str(file_get_contents('php://input'), $vars);
+ break;
+ default:
+ $vars = $_GET;
+ }
+ $params = $method->getParameters();
+ $paramsBindType = C('URL_PARAMS_BIND_TYPE');
+ foreach ($params as $param) {
+ $name = $param->getName();
+ if (1 == $paramsBindType && !empty($vars)) {
+ $args[] = array_shift($vars);
+ } elseif (0 == $paramsBindType && isset($vars[$name])) {
+ $args[] = $vars[$name];
+ } elseif ($param->isDefaultValueAvailable()) {
+ $args[] = $param->getDefaultValue();
+ } else {
+ E(L('_PARAM_ERROR_') . ':' . $name);
+ }
+ }
+ // 开启绑定参数过滤机制
+ if (C('URL_PARAMS_SAFE')) {
+ $filters = C('URL_PARAMS_FILTER') ?: C('DEFAULT_FILTER');
+ if ($filters) {
+ $filters = explode(',', $filters);
+ foreach ($filters as $filter) {
+ $args = array_map_recursive($filter, $args); // 参数过滤
+ }
+ }
+ }
+ array_walk_recursive($args, 'think_filter');
+ $method->invokeArgs($module, $args);
+ } else {
+ $method->invoke($module);
+ }
+ } else {
+ // 操作方法不是Public 抛出异常
+ throw new \ReflectionException();
+ }
+ } catch (\ReflectionException $e) {
+ // 方法调用发生异常后 引导到__call方法处理
+ $method = new \ReflectionMethod($module, '__call');
+ $method->invokeArgs($module, array($action, ''));
+ }
+ return;
+ }
+
+ /**
+ * 运行应用实例 入口文件使用的快捷方法
+ * @access public
+ * @return void
+ */
+ public static function run()
+ {
+ App::init();
+ // Session初始化
+ if (!IS_CLI) {
+ session(C('SESSION_OPTIONS'));
+ }
+ // 记录应用初始化时间
+ G('initTime');
+ App::exec();
+ return;
+ }
+
+}
diff --git a/Framework/Mode/Lite/Controller.class.php b/Framework/Mode/Lite/Controller.class.php
new file mode 100644
index 00000000..3e021ec9
--- /dev/null
+++ b/Framework/Mode/Lite/Controller.class.php
@@ -0,0 +1,321 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * ThinkPHP 控制器基类 抽象类
+ */
+abstract class Controller
+{
+
+ /**
+ * 视图实例对象
+ * @var view
+ * @access protected
+ */
+ protected $view = null;
+
+ /**
+ * 控制器参数
+ * @var config
+ * @access protected
+ */
+ protected $config = array();
+
+ /**
+ * 架构函数 取得模板对象实例
+ * @access public
+ */
+ public function __construct()
+ {
+ //实例化视图类
+ $this->view = Think::instance('Think\View');
+ //控制器初始化
+ if (method_exists($this, '_initialize')) {
+ $this->_initialize();
+ }
+
+ }
+
+ /**
+ * 模板显示 调用内置的模板引擎显示方法,
+ * @access protected
+ * @param string $templateFile 指定要调用的模板文件
+ * 默认为空 由系统自动定位模板文件
+ * @param string $charset 输出编码
+ * @param string $contentType 输出类型
+ * @param string $content 输出内容
+ * @param string $prefix 模板缓存前缀
+ * @return void
+ */
+ protected function display($templateFile = '', $charset = '', $contentType = '', $content = '', $prefix = '')
+ {
+ $this->view->display($templateFile, $charset, $contentType, $content, $prefix);
+ }
+
+ /**
+ * 输出内容文本可以包括Html 并支持内容解析
+ * @access protected
+ * @param string $content 输出内容
+ * @param string $charset 模板输出字符集
+ * @param string $contentType 输出类型
+ * @param string $prefix 模板缓存前缀
+ * @return mixed
+ */
+ protected function show($content, $charset = '', $contentType = '', $prefix = '')
+ {
+ $this->view->display('', $charset, $contentType, $content, $prefix);
+ }
+
+ /**
+ * 获取输出页面内容
+ * 调用内置的模板引擎fetch方法,
+ * @access protected
+ * @param string $templateFile 指定要调用的模板文件
+ * 默认为空 由系统自动定位模板文件
+ * @param string $content 模板输出内容
+ * @param string $prefix 模板缓存前缀*
+ * @return string
+ */
+ protected function fetch($templateFile = '', $content = '', $prefix = '')
+ {
+ return $this->view->fetch($templateFile, $content, $prefix);
+ }
+
+ /**
+ * 模板主题设置
+ * @access protected
+ * @param string $theme 模版主题
+ * @return Action
+ */
+ protected function theme($theme)
+ {
+ $this->view->theme($theme);
+ return $this;
+ }
+
+ /**
+ * 模板变量赋值
+ * @access protected
+ * @param mixed $name 要显示的模板变量
+ * @param mixed $value 变量的值
+ * @return Action
+ */
+ protected function assign($name, $value = '')
+ {
+ $this->view->assign($name, $value);
+ return $this;
+ }
+
+ public function __set($name, $value)
+ {
+ $this->assign($name, $value);
+ }
+
+ /**
+ * 取得模板显示变量的值
+ * @access protected
+ * @param string $name 模板显示变量
+ * @return mixed
+ */
+ public function get($name = '')
+ {
+ return $this->view->get($name);
+ }
+
+ public function __get($name)
+ {
+ return $this->get($name);
+ }
+
+ /**
+ * 检测模板变量的值
+ * @access public
+ * @param string $name 名称
+ * @return boolean
+ */
+ public function __isset($name)
+ {
+ return $this->get($name);
+ }
+
+ /**
+ * 魔术方法 有不存在的操作的时候执行
+ * @access public
+ * @param string $method 方法名
+ * @param array $args 参数
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ if (0 === strcasecmp($method, ACTION_NAME . C('ACTION_SUFFIX'))) {
+ if (method_exists($this, '_empty')) {
+ // 如果定义了_empty操作 则调用
+ $this->_empty($method, $args);
+ } elseif (file_exists_case($this->view->parseTemplate())) {
+ // 检查是否存在默认模版 如果有直接输出模版
+ $this->display();
+ } else {
+ E(L('_ERROR_ACTION_') . ':' . ACTION_NAME);
+ }
+ } else {
+ E(__CLASS__ . ':' . $method . L('_METHOD_NOT_EXIST_'));
+ return;
+ }
+ }
+
+ /**
+ * 操作错误跳转的快捷方法
+ * @access protected
+ * @param string $message 错误信息
+ * @param string $jumpUrl 页面跳转地址
+ * @param mixed $ajax 是否为Ajax方式 当数字时指定跳转时间
+ * @return void
+ */
+ protected function error($message = '', $jumpUrl = '', $ajax = false)
+ {
+ $this->dispatchJump($message, 0, $jumpUrl, $ajax);
+ }
+
+ /**
+ * 操作成功跳转的快捷方法
+ * @access protected
+ * @param string $message 提示信息
+ * @param string $jumpUrl 页面跳转地址
+ * @param mixed $ajax 是否为Ajax方式 当数字时指定跳转时间
+ * @return void
+ */
+ protected function success($message = '', $jumpUrl = '', $ajax = false)
+ {
+ $this->dispatchJump($message, 1, $jumpUrl, $ajax);
+ }
+
+ /**
+ * Ajax方式返回数据到客户端
+ * @access protected
+ * @param mixed $data 要返回的数据
+ * @param String $type AJAX返回数据格式
+ * @param int $json_option 传递给json_encode的option参数
+ * @return void
+ */
+ protected function ajaxReturn($data, $type = '', $json_option = 0)
+ {
+ if (empty($type)) {
+ $type = C('DEFAULT_AJAX_RETURN');
+ }
+
+ switch (strtoupper($type)) {
+ case 'JSON':
+ // 返回JSON数据格式到客户端 包含状态信息
+ header('Content-Type:application/json; charset=utf-8');
+ $data = json_encode($data, $json_option);
+ break;
+ case 'JSONP':
+ // 返回JSON数据格式到客户端 包含状态信息
+ header('Content-Type:application/json; charset=utf-8');
+ $handler = isset($_GET[C('VAR_JSONP_HANDLER')]) ? $_GET[C('VAR_JSONP_HANDLER')] : C('DEFAULT_JSONP_HANDLER');
+ $data = $handler . '(' . json_encode($data, $json_option) . ');';
+ break;
+ case 'EVAL':
+ // 返回可执行的js脚本
+ header('Content-Type:text/html; charset=utf-8');
+ break;
+ }
+ exit($data);
+ }
+
+ /**
+ * Action跳转(URL重定向) 支持指定模块和延时跳转
+ * @access protected
+ * @param string $url 跳转的URL表达式
+ * @param array $params 其它URL参数
+ * @param integer $delay 延时跳转的时间 单位为秒
+ * @param string $msg 跳转提示信息
+ * @return void
+ */
+ protected function redirect($url, $params = array(), $delay = 0, $msg = '')
+ {
+ $url = U($url, $params);
+ redirect($url, $delay, $msg);
+ }
+
+ /**
+ * 默认跳转操作 支持错误导向和正确跳转
+ * 调用模板显示 默认为public目录下面的success页面
+ * 提示页面为可配置 支持模板标签
+ * @param string $message 提示信息
+ * @param Boolean $status 状态
+ * @param string $jumpUrl 页面跳转地址
+ * @param mixed $ajax 是否为Ajax方式 当数字时指定跳转时间
+ * @access private
+ * @return void
+ */
+ private function dispatchJump($message, $status = 1, $jumpUrl = '', $ajax = false)
+ {
+ if (true === $ajax || IS_AJAX) {
+// AJAX提交
+ $data = is_array($ajax) ? $ajax : array();
+ $data['info'] = $message;
+ $data['status'] = $status;
+ $data['url'] = $jumpUrl;
+ $this->ajaxReturn($data);
+ }
+ if (is_int($ajax)) {
+ $this->assign('waitSecond', $ajax);
+ }
+
+ if (!empty($jumpUrl)) {
+ $this->assign('jumpUrl', $jumpUrl);
+ }
+
+ // 提示标题
+ $this->assign('msgTitle', $status ? L('_OPERATION_SUCCESS_') : L('_OPERATION_FAIL_'));
+ //如果设置了关闭窗口,则提示完毕后自动关闭窗口
+ if ($this->get('closeWin')) {
+ $this->assign('jumpUrl', 'javascript:window.close();');
+ }
+
+ $this->assign('status', $status); // 状态
+ //保证输出不受静态缓存影响
+ C('HTML_CACHE_ON', false);
+ if ($status) {
+ //发送成功信息
+ $this->assign('message', $message); // 提示信息
+ // 成功操作后默认停留1秒
+ if (!isset($this->waitSecond)) {
+ $this->assign('waitSecond', '1');
+ }
+
+ // 默认操作成功自动返回操作前页面
+ if (!isset($this->jumpUrl)) {
+ $this->assign("jumpUrl", $_SERVER["HTTP_REFERER"]);
+ }
+
+ $this->display(C('TMPL_ACTION_SUCCESS'));
+ } else {
+ $this->assign('error', $message); // 提示信息
+ //发生错误时候默认停留3秒
+ if (!isset($this->waitSecond)) {
+ $this->assign('waitSecond', '3');
+ }
+
+ // 默认发生错误的话自动返回上页
+ if (!isset($this->jumpUrl)) {
+ $this->assign('jumpUrl', "javascript:history.back(-1);");
+ }
+
+ $this->display(C('TMPL_ACTION_ERROR'));
+ // 中止执行 避免出错后继续执行
+ exit;
+ }
+ }
+
+}
diff --git a/Framework/Mode/Lite/Dispatcher.class.php b/Framework/Mode/Lite/Dispatcher.class.php
new file mode 100644
index 00000000..6e471ca4
--- /dev/null
+++ b/Framework/Mode/Lite/Dispatcher.class.php
@@ -0,0 +1,290 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * ThinkPHP内置的Dispatcher类
+ * 完成URL解析、路由和调度
+ */
+
+class Dispatcher
+{
+
+ /**
+ * URL映射到控制器
+ * @access public
+ * @return void
+ */
+ public static function dispatch()
+ {
+ $varPath = C('VAR_PATHINFO');
+ $varModule = C('VAR_MODULE');
+ $varController = C('VAR_CONTROLLER');
+ $varAction = C('VAR_ACTION');
+ $urlCase = C('URL_CASE_INSENSITIVE');
+ if (isset($_GET[$varPath])) {
+ // 判断URL里面是否有兼容模式参数
+ $_SERVER['PATH_INFO'] = $_GET[$varPath];
+ unset($_GET[$varPath]);
+ } elseif (IS_CLI) {
+ // CLI模式下 index.php module/controller/action/params/...
+ $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
+ }
+
+ // 开启子域名部署
+ if (C('APP_SUB_DOMAIN_DEPLOY')) {
+ $rules = C('APP_SUB_DOMAIN_RULES');
+ if (isset($rules[$_SERVER['HTTP_HOST']])) {
+ // 完整域名或者IP配置
+ define('APP_DOMAIN', $_SERVER['HTTP_HOST']); // 当前完整域名
+ $rule = $rules[APP_DOMAIN];
+ } else {
+ if (strpos(C('APP_DOMAIN_SUFFIX'), '.')) {
+ // com.cn net.cn
+ $domain = array_slice(explode('.', $_SERVER['HTTP_HOST']), 0, -3);
+ } else {
+ $domain = array_slice(explode('.', $_SERVER['HTTP_HOST']), 0, -2);
+ }
+ if (!empty($domain)) {
+ $subDomain = implode('.', $domain);
+ define('SUB_DOMAIN', $subDomain); // 当前完整子域名
+ $domain2 = array_pop($domain); // 二级域名
+ if ($domain) {
+ // 存在三级域名
+ $domain3 = array_pop($domain);
+ }
+ if (isset($rules[$subDomain])) {
+ // 子域名
+ $rule = $rules[$subDomain];
+ } elseif (isset($rules['*.' . $domain2]) && !empty($domain3)) {
+ // 泛三级域名
+ $rule = $rules['*.' . $domain2];
+ $panDomain = $domain3;
+ } elseif (isset($rules['*']) && !empty($domain2) && 'www' != $domain2) {
+ // 泛二级域名
+ $rule = $rules['*'];
+ $panDomain = $domain2;
+ }
+ }
+ }
+
+ if (!empty($rule)) {
+ // 子域名部署规则 '子域名'=>array('模块名','var1=a&var2=b');
+ if (is_array($rule)) {
+ list($rule, $vars) = $rule;
+ }
+ $array = explode('/', $rule);
+ // 模块绑定
+ define('BIND_MODULE', array_shift($array));
+
+ if (isset($vars)) {
+ // 传入参数
+ parse_str($vars, $parms);
+ if (isset($panDomain)) {
+ $pos = array_search('*', $parms);
+ if (false !== $pos) {
+ // 泛域名作为参数
+ $parms[$pos] = $panDomain;
+ }
+ }
+ $_GET = array_merge($_GET, $parms);
+ }
+ }
+ }
+ // 分析PATHINFO信息
+ if (!isset($_SERVER['PATH_INFO'])) {
+ $types = explode(',', C('URL_PATHINFO_FETCH'));
+ foreach ($types as $type) {
+ if (0 === strpos($type, ':')) {
+// 支持函数判断
+ $_SERVER['PATH_INFO'] = call_user_func(substr($type, 1));
+ break;
+ } elseif (!empty($_SERVER[$type])) {
+ $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ?
+ substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type];
+ break;
+ }
+ }
+ }
+
+ $depr = C('URL_PATHINFO_DEPR');
+ define('MODULE_PATHINFO_DEPR', $depr);
+
+ if (empty($_SERVER['PATH_INFO'])) {
+ $_SERVER['PATH_INFO'] = '';
+ define('__INFO__', '');
+ define('__EXT__', '');
+ } else {
+ define('__INFO__', trim($_SERVER['PATH_INFO'], '/'));
+ // URL后缀
+ define('__EXT__', strtolower(pathinfo($_SERVER['PATH_INFO'], PATHINFO_EXTENSION)));
+ $_SERVER['PATH_INFO'] = __INFO__;
+ if (!defined('BIND_MODULE') && (!C('URL_ROUTER_ON') || !Route::check())) {
+ if (__INFO__) {
+ // 获取模块名
+ $paths = explode($depr, __INFO__, 2);
+ $allowList = C('MODULE_ALLOW_LIST'); // 允许的模块列表
+ $module = preg_replace('/\.' . __EXT__ . '$/i', '', $paths[0]);
+ if (empty($allowList) || (is_array($allowList) && in_array_case($module, $allowList))) {
+ $_GET[$varModule] = $module;
+ $_SERVER['PATH_INFO'] = isset($paths[1]) ? $paths[1] : '';
+ }
+ }
+ }
+ }
+
+ // URL常量
+ define('__SELF__', strip_tags($_SERVER[C('URL_REQUEST_URI')]));
+
+ // 获取模块名称
+ define('MODULE_NAME', defined('BIND_MODULE') ? BIND_MODULE : self::getModule($varModule));
+
+ // 检测模块是否存在
+ if (MODULE_NAME && (defined('BIND_MODULE') || !in_array_case(MODULE_NAME, C('MODULE_DENY_LIST'))) && is_dir(APP_PATH . MODULE_NAME)) {
+ // 定义当前模块路径
+ define('MODULE_PATH', APP_PATH . MODULE_NAME . '/');
+ // 定义当前模块的模版缓存路径
+ C('CACHE_PATH', CACHE_PATH . MODULE_NAME . '/');
+ // 定义当前模块的日志目录
+ C('LOG_PATH', realpath(LOG_PATH) . '/' . MODULE_NAME . '/');
+
+ // 加载模块配置文件
+ if (is_file(MODULE_PATH . 'Conf/config' . CONF_EXT)) {
+ C(load_config(MODULE_PATH . 'Conf/config' . CONF_EXT));
+ }
+
+ // 加载模块别名定义
+ if (is_file(MODULE_PATH . 'Conf/alias.php')) {
+ Think::addMap(include MODULE_PATH . 'Conf/alias.php');
+ }
+
+ // 加载模块函数文件
+ if (is_file(MODULE_PATH . 'Common/function.php')) {
+ include MODULE_PATH . 'Common/function.php';
+ }
+
+ } else {
+ E(L('_MODULE_NOT_EXIST_') . ':' . MODULE_NAME);
+ }
+
+ if (!defined('__APP__')) {
+ $urlMode = C('URL_MODEL');
+ if (URL_COMPAT == $urlMode) {
+// 兼容模式判断
+ define('PHP_FILE', _PHP_FILE_ . '?' . $varPath . '=');
+ } elseif (URL_REWRITE == $urlMode) {
+ $url = dirname(_PHP_FILE_);
+ if ('/' == $url || '\\' == $url) {
+ $url = '';
+ }
+
+ define('PHP_FILE', $url);
+ } else {
+ define('PHP_FILE', _PHP_FILE_);
+ }
+ // 当前应用地址
+ define('__APP__', strip_tags(PHP_FILE));
+ }
+ // 模块URL地址
+ $moduleName = defined('MODULE_ALIAS') ? MODULE_ALIAS : MODULE_NAME;
+ define('__MODULE__', defined('BIND_MODULE') ? __APP__ : __APP__ . '/' . ($urlCase ? strtolower($moduleName) : $moduleName));
+
+ if ('' != $_SERVER['PATH_INFO'] && (!C('URL_ROUTER_ON') || !Route::check())) {
+ // 检测路由规则 如果没有则按默认规则调度URL
+ // 检查禁止访问的URL后缀
+ if (C('URL_DENY_SUFFIX') && preg_match('/\.(' . trim(C('URL_DENY_SUFFIX'), '.') . ')$/i', $_SERVER['PATH_INFO'])) {
+ send_http_status(404);
+ exit;
+ }
+
+ // 去除URL后缀
+ $_SERVER['PATH_INFO'] = preg_replace(C('URL_HTML_SUFFIX') ? '/\.(' . trim(C('URL_HTML_SUFFIX'), '.') . ')$/i' : '/\.' . __EXT__ . '$/i', '', $_SERVER['PATH_INFO']);
+
+ $depr = C('URL_PATHINFO_DEPR');
+ $paths = explode($depr, trim($_SERVER['PATH_INFO'], $depr));
+
+ $_GET[$varController] = array_shift($paths);
+ // 获取操作
+ $_GET[$varAction] = array_shift($paths);
+
+ // 解析剩余的URL参数
+ $var = array();
+ if (C('URL_PARAMS_BIND') && 1 == C('URL_PARAMS_BIND_TYPE')) {
+ // URL参数按顺序绑定变量
+ $var = $paths;
+ } else {
+ preg_replace_callback('/(\w+)\/([^\/]+)/', function ($match) use (&$var) {$var[$match[1]] = strip_tags($match[2]);}, implode('/', $paths));
+ }
+ $_GET = array_merge($var, $_GET);
+ }
+ // 获取控制器和操作名
+ define('CONTROLLER_NAME', self::getController($varController, $urlCase));
+ define('ACTION_NAME', self::getAction($varAction, $urlCase));
+
+ // 当前控制器的UR地址
+ define('__CONTROLLER__', __MODULE__ . $depr . ($urlCase ? parse_name(CONTROLLER_NAME) : CONTROLLER_NAME));
+
+ // 当前操作的URL地址
+ define('__ACTION__', __CONTROLLER__ . $depr . ACTION_NAME);
+
+ //保证$_REQUEST正常取值
+ $_REQUEST = array_merge($_POST, $_GET);
+ }
+
+ /**
+ * 获得实际的控制器名称
+ */
+ private static function getController($var, $urlCase)
+ {
+ $controller = (!empty($_GET[$var]) ? $_GET[$var] : C('DEFAULT_CONTROLLER'));
+ unset($_GET[$var]);
+ if ($urlCase) {
+ // URL地址不区分大小写
+ // 智能识别方式 user_type 识别到 UserTypeController 控制器
+ $controller = parse_name($controller, 1);
+ }
+ return strip_tags(ucfirst($controller));
+ }
+
+ /**
+ * 获得实际的操作名称
+ */
+ private static function getAction($var, $urlCase)
+ {
+ $action = !empty($_POST[$var]) ?
+ $_POST[$var] :
+ (!empty($_GET[$var]) ? $_GET[$var] : C('DEFAULT_ACTION'));
+ unset($_POST[$var], $_GET[$var]);
+ return strip_tags($urlCase ? strtolower($action) : $action);
+ }
+
+ /**
+ * 获得实际的模块名称
+ */
+ private static function getModule($var)
+ {
+ $module = (!empty($_GET[$var]) ? $_GET[$var] : C('DEFAULT_MODULE'));
+ unset($_GET[$var]);
+ if ($maps = C('URL_MODULE_MAP')) {
+ if (isset($maps[strtolower($module)])) {
+ // 记录当前别名
+ define('MODULE_ALIAS', strtolower($module));
+ // 获取实际的模块名
+ return ucfirst($maps[MODULE_ALIAS]);
+ } elseif (array_search(strtolower($module), $maps)) {
+ // 禁止访问原始模块
+ return '';
+ }
+ }
+ return strip_tags(ucfirst($module));
+ }
+
+}
diff --git a/Framework/Mode/Lite/Model.class.php b/Framework/Mode/Lite/Model.class.php
new file mode 100644
index 00000000..e2269cd6
--- /dev/null
+++ b/Framework/Mode/Lite/Model.class.php
@@ -0,0 +1,1620 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * ThinkPHP Model模型类
+ * 实现了ORM和ActiveRecords模式
+ */
+class Model
+{
+
+ // 当前数据库操作对象
+ protected $db = null;
+ // 数据库对象池
+ private $_db = array();
+ // 主键名称
+ protected $pk = 'id';
+ // 主键是否自动增长
+ protected $autoinc = false;
+ // 数据表前缀
+ protected $tablePrefix = null;
+ // 模型名称
+ protected $name = '';
+ // 数据库名称
+ protected $dbName = '';
+ //数据库配置
+ protected $connection = '';
+ // 数据表名(不包含表前缀)
+ protected $tableName = '';
+ // 实际数据表名(包含表前缀)
+ protected $trueTableName = '';
+ // 最近错误信息
+ protected $error = '';
+ // 字段信息
+ protected $fields = array();
+ // 数据信息
+ protected $data = array();
+ // 查询表达式参数
+ protected $options = array();
+ protected $_validate = array(); // 自动验证定义
+ protected $_auto = array(); // 自动完成定义
+ protected $_map = array(); // 字段映射定义
+ protected $_scope = array(); // 命名范围定义
+ // 是否自动检测数据表字段信息
+ protected $autoCheckFields = true;
+ // 是否批处理验证
+ protected $patchValidate = false;
+ // 链操作方法列表
+ protected $methods = array('strict', 'order', 'alias', 'having', 'group', 'lock', 'distinct', 'auto', 'filter', 'validate', 'result', 'token', 'index', 'force');
+
+ /**
+ * 架构函数
+ * 取得DB类的实例对象 字段检查
+ * @access public
+ * @param string $name 模型名称
+ * @param string $tablePrefix 表前缀
+ * @param mixed $connection 数据库连接信息
+ */
+ public function __construct($name = '', $tablePrefix = '', $connection = '')
+ {
+ // 模型初始化
+ $this->_initialize();
+ // 获取模型名称
+ if (!empty($name)) {
+ if (strpos($name, '.')) {
+ // 支持 数据库名.模型名的 定义
+ list($this->dbName, $this->name) = explode('.', $name);
+ } else {
+ $this->name = $name;
+ }
+ } elseif (empty($this->name)) {
+ $this->name = $this->getModelName();
+ }
+ // 设置表前缀
+ if (is_null($tablePrefix)) {
+// 前缀为Null表示没有前缀
+ $this->tablePrefix = '';
+ } elseif ('' != $tablePrefix) {
+ $this->tablePrefix = $tablePrefix;
+ } elseif (!isset($this->tablePrefix)) {
+ $this->tablePrefix = C('DB_PREFIX');
+ }
+
+ // 数据库初始化操作
+ // 获取数据库操作对象
+ // 当前模型有独立的数据库连接信息
+ $this->db(0, empty($this->connection) ? $connection : $this->connection, true);
+ }
+
+ /**
+ * 自动检测数据表信息
+ * @access protected
+ * @return void
+ */
+ protected function _checkTableInfo()
+ {
+ // 如果不是Model类 自动记录数据表信息
+ // 只在第一次执行记录
+ if (empty($this->fields)) {
+ // 如果数据表字段没有定义则自动获取
+ if (C('DB_FIELDS_CACHE')) {
+ $db = $this->dbName ?: C('DB_NAME');
+ $fields = F('_fields/' . strtolower($db . '.' . $this->tablePrefix . $this->name));
+ if ($fields) {
+ $this->fields = $fields;
+ if (!empty($fields['_pk'])) {
+ $this->pk = $fields['_pk'];
+ }
+ return;
+ }
+ }
+ // 每次都会读取数据表信息
+ $this->flush();
+ }
+ }
+
+ /**
+ * 获取字段信息并缓存
+ * @access public
+ * @return void
+ */
+ public function flush()
+ {
+ // 缓存不存在则查询数据表信息
+ $this->db->setModel($this->name);
+ $fields = $this->db->getFields($this->getTableName());
+ if (!$fields) {
+ // 无法获取字段信息
+ return false;
+ }
+ $this->fields = array_keys($fields);
+ unset($this->fields['_pk']);
+ foreach ($fields as $key => $val) {
+ // 记录字段类型
+ $type[$key] = $val['type'];
+ if ($val['primary']) {
+ // 增加复合主键支持
+ if (isset($this->fields['_pk']) && null != $this->fields['_pk']) {
+ if (is_string($this->fields['_pk'])) {
+ $this->pk = array($this->fields['_pk']);
+ $this->fields['_pk'] = $this->pk;
+ }
+ $this->pk[] = $key;
+ $this->fields['_pk'][] = $key;
+ } else {
+ $this->pk = $key;
+ $this->fields['_pk'] = $key;
+ }
+ if ($val['autoinc']) {
+ $this->autoinc = true;
+ }
+
+ }
+ }
+ // 记录字段类型信息
+ $this->fields['_type'] = $type;
+
+ // 2008-3-7 增加缓存开关控制
+ if (C('DB_FIELDS_CACHE')) {
+ // 永久缓存数据表信息
+ $db = $this->dbName ?: C('DB_NAME');
+ F('_fields/' . strtolower($db . '.' . $this->tablePrefix . $this->name), $this->fields);
+ }
+ }
+
+ /**
+ * 设置数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @param mixed $value 值
+ * @return void
+ */
+ public function __set($name, $value)
+ {
+ // 设置数据对象属性
+ $this->data[$name] = $value;
+ }
+
+ /**
+ * 获取数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return isset($this->data[$name]) ? $this->data[$name] : null;
+ }
+
+ /**
+ * 检测数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @return boolean
+ */
+ public function __isset($name)
+ {
+ return isset($this->data[$name]);
+ }
+
+ /**
+ * 销毁数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @return void
+ */
+ public function __unset($name)
+ {
+ unset($this->data[$name]);
+ }
+
+ /**
+ * 利用__call方法实现一些特殊的Model方法
+ * @access public
+ * @param string $method 方法名称
+ * @param array $args 调用参数
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ if (in_array(strtolower($method), $this->methods, true)) {
+ // 连贯操作的实现
+ $this->options[strtolower($method)] = $args[0];
+ return $this;
+ } elseif (in_array(strtolower($method), array('count', 'sum', 'min', 'max', 'avg'), true)) {
+ // 统计查询的实现
+ $field = isset($args[0]) ? $args[0] : '*';
+ return $this->getField(strtoupper($method) . '(' . $field . ') AS tp_' . $method);
+ } elseif (strtolower(substr($method, 0, 5)) == 'getby') {
+ // 根据某个字段获取记录
+ $field = parse_name(substr($method, 5));
+ $where[$field] = $args[0];
+ return $this->where($where)->find();
+ } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
+ // 根据某个字段获取记录的某个值
+ $name = parse_name(substr($method, 10));
+ $where[$name] = $args[0];
+ return $this->where($where)->getField($args[1]);
+ } elseif (isset($this->_scope[$method])) {
+// 命名范围的单独调用支持
+ return $this->scope($method, $args[0]);
+ } else {
+ E(__CLASS__ . ':' . $method . L('_METHOD_NOT_EXIST_'));
+ return;
+ }
+ }
+ // 回调方法 初始化模型
+ protected function _initialize()
+ {}
+
+ /**
+ * 对保存到数据库的数据进行处理
+ * @access protected
+ * @param mixed $data 要操作的数据
+ * @return boolean
+ */
+ protected function _facade($data)
+ {
+
+ // 检查数据字段合法性
+ if (!empty($this->fields)) {
+ if (!empty($this->options['field'])) {
+ $fields = $this->options['field'];
+ unset($this->options['field']);
+ if (is_string($fields)) {
+ $fields = explode(',', $fields);
+ }
+ } else {
+ $fields = $this->fields;
+ }
+ foreach ($data as $key => $val) {
+ if (!in_array($key, $fields, true)) {
+ if (!empty($this->options['strict'])) {
+ E(L('_DATA_TYPE_INVALID_') . ':[' . $key . '=>' . $val . ']');
+ }
+ unset($data[$key]);
+ } elseif (is_scalar($val)) {
+ // 字段类型检查 和 强制转换
+ $this->_parseType($data, $key);
+ }
+ }
+ }
+
+ // 安全过滤
+ if (!empty($this->options['filter'])) {
+ $data = array_map($this->options['filter'], $data);
+ unset($this->options['filter']);
+ }
+ $this->_before_write($data);
+ return $data;
+ }
+
+ // 写入数据前的回调方法 包括新增和更新
+ protected function _before_write(&$data)
+ {}
+
+ /**
+ * 新增数据
+ * @access public
+ * @param mixed $data 数据
+ * @param array $options 表达式
+ * @param boolean $replace 是否replace
+ * @return mixed
+ */
+ public function add($data = '', $options = array(), $replace = false)
+ {
+ if (empty($data)) {
+ // 没有传递数据,获取当前数据对象的值
+ if (!empty($this->data)) {
+ $data = $this->data;
+ // 重置数据
+ $this->data = array();
+ } else {
+ $this->error = L('_DATA_TYPE_INVALID_');
+ return false;
+ }
+ }
+ // 数据处理
+ $data = $this->_facade($data);
+ // 分析表达式
+ $options = $this->_parseOptions($options);
+ if (false === $this->_before_insert($data, $options)) {
+ return false;
+ }
+ // 写入数据到数据库
+ $result = $this->db->insert($data, $options, $replace);
+ if (false !== $result && is_numeric($result)) {
+ $pk = $this->getPk();
+ // 增加复合主键支持
+ if (is_array($pk)) {
+ return $result;
+ }
+
+ $insertId = $this->getLastInsID();
+ if ($insertId) {
+ // 自增主键返回插入ID
+ $data[$pk] = $insertId;
+ if (false === $this->_after_insert($data, $options)) {
+ return false;
+ }
+ return $insertId;
+ }
+ if (false === $this->_after_insert($data, $options)) {
+ return false;
+ }
+ }
+ return $result;
+ }
+ // 插入数据前的回调方法
+ protected function _before_insert(&$data, $options)
+ {}
+ // 插入成功后的回调方法
+ protected function _after_insert($data, $options)
+ {}
+
+ public function addAll($dataList, $options = array(), $replace = false)
+ {
+ if (empty($dataList)) {
+ $this->error = L('_DATA_TYPE_INVALID_');
+ return false;
+ }
+ // 数据处理
+ foreach ($dataList as $key => $data) {
+ $dataList[$key] = $this->_facade($data);
+ }
+ // 分析表达式
+ $options = $this->_parseOptions($options);
+ // 写入数据到数据库
+ $result = $this->db->insertAll($dataList, $options, $replace);
+ if (false !== $result) {
+ $insertId = $this->getLastInsID();
+ if ($insertId) {
+ return $insertId;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * 保存数据
+ * @access public
+ * @param mixed $data 数据
+ * @param array $options 表达式
+ * @return boolean
+ */
+ public function save($data = '', $options = array())
+ {
+ if (empty($data)) {
+ // 没有传递数据,获取当前数据对象的值
+ if (!empty($this->data)) {
+ $data = $this->data;
+ // 重置数据
+ $this->data = array();
+ } else {
+ $this->error = L('_DATA_TYPE_INVALID_');
+ return false;
+ }
+ }
+ // 数据处理
+ $data = $this->_facade($data);
+ if (empty($data)) {
+ // 没有数据则不执行
+ $this->error = L('_DATA_TYPE_INVALID_');
+ return false;
+ }
+ // 分析表达式
+ $options = $this->_parseOptions($options);
+ $pk = $this->getPk();
+ if (!isset($options['where'])) {
+ // 如果存在主键数据 则自动作为更新条件
+ if (is_string($pk) && isset($data[$pk])) {
+ $where[$pk] = $data[$pk];
+ unset($data[$pk]);
+ } elseif (is_array($pk)) {
+ // 增加复合主键支持
+ foreach ($pk as $field) {
+ if (isset($data[$field])) {
+ $where[$field] = $data[$field];
+ } else {
+ // 如果缺少复合主键数据则不执行
+ $this->error = L('_OPERATION_WRONG_');
+ return false;
+ }
+ unset($data[$field]);
+ }
+ }
+ if (!isset($where)) {
+ // 如果没有任何更新条件则不执行
+ $this->error = L('_OPERATION_WRONG_');
+ return false;
+ } else {
+ $options['where'] = $where;
+ }
+ }
+
+ if (is_array($options['where']) && isset($options['where'][$pk])) {
+ $pkValue = $options['where'][$pk];
+ }
+ if (false === $this->_before_update($data, $options)) {
+ return false;
+ }
+ $result = $this->db->update($data, $options);
+ if (false !== $result && is_numeric($result)) {
+ if (isset($pkValue)) {
+ $data[$pk] = $pkValue;
+ }
+
+ $this->_after_update($data, $options);
+ }
+ return $result;
+ }
+ // 更新数据前的回调方法
+ protected function _before_update(&$data, $options)
+ {}
+ // 更新成功后的回调方法
+ protected function _after_update($data, $options)
+ {}
+
+ /**
+ * 删除数据
+ * @access public
+ * @param mixed $options 表达式
+ * @return mixed
+ */
+ public function delete($options = array())
+ {
+ $pk = $this->getPk();
+ if (empty($options) && empty($this->options['where'])) {
+ // 如果删除条件为空 则删除当前数据对象所对应的记录
+ if (!empty($this->data) && isset($this->data[$pk])) {
+ return $this->delete($this->data[$pk]);
+ } else {
+ return false;
+ }
+
+ }
+ if (is_numeric($options) || is_string($options)) {
+ // 根据主键删除记录
+ if (strpos($options, ',')) {
+ $where[$pk] = array('IN', $options);
+ } else {
+ $where[$pk] = $options;
+ }
+ $options = array();
+ $options['where'] = $where;
+ }
+ // 根据复合主键删除记录
+ if (is_array($options) && (count($options) > 0) && is_array($pk)) {
+ $count = 0;
+ foreach (array_keys($options) as $key) {
+ if (is_int($key)) {
+ $count++;
+ }
+
+ }
+ if (count($pk) == $count) {
+ $i = 0;
+ foreach ($pk as $field) {
+ $where[$field] = $options[$i];
+ unset($options[$i++]);
+ }
+ $options['where'] = $where;
+ } else {
+ return false;
+ }
+ }
+ // 分析表达式
+ $options = $this->_parseOptions($options);
+ if (empty($options['where'])) {
+ // 如果条件为空 不进行删除操作 除非设置 1=1
+ return false;
+ }
+ if (is_array($options['where']) && isset($options['where'][$pk])) {
+ $pkValue = $options['where'][$pk];
+ }
+
+ if (false === $this->_before_delete($options)) {
+ return false;
+ }
+ $result = $this->db->delete($options);
+ if (false !== $result && is_numeric($result)) {
+ $data = array();
+ if (isset($pkValue)) {
+ $data[$pk] = $pkValue;
+ }
+
+ $this->_after_delete($data, $options);
+ }
+ // 返回删除记录个数
+ return $result;
+ }
+ // 删除数据前的回调方法
+ protected function _before_delete($options)
+ {}
+ // 删除成功后的回调方法
+ protected function _after_delete($data, $options)
+ {}
+
+ /**
+ * 查询数据集
+ * @access public
+ * @param array $options 表达式参数
+ * @return mixed
+ */
+ public function select($options = array())
+ {
+ $pk = $this->getPk();
+ if (is_string($options) || is_numeric($options)) {
+ // 根据主键查询
+ if (strpos($options, ',')) {
+ $where[$pk] = array('IN', $options);
+ } else {
+ $where[$pk] = $options;
+ }
+ $options = array();
+ $options['where'] = $where;
+ } elseif (is_array($options) && (count($options) > 0) && is_array($pk)) {
+ // 根据复合主键查询
+ $count = 0;
+ foreach (array_keys($options) as $key) {
+ if (is_int($key)) {
+ $count++;
+ }
+
+ }
+ if (count($pk) == $count) {
+ $i = 0;
+ foreach ($pk as $field) {
+ $where[$field] = $options[$i];
+ unset($options[$i++]);
+ }
+ $options['where'] = $where;
+ } else {
+ return false;
+ }
+ } elseif (false === $options) {
+ // 用于子查询 不查询只返回SQL
+ $options = array();
+ // 分析表达式
+ $options = $this->_parseOptions($options);
+ return '( ' . $this->fetchSql(true)->select($options) . ' )';
+ }
+ // 分析表达式
+ $options = $this->_parseOptions($options);
+ // 判断查询缓存
+ if (isset($options['cache'])) {
+ $cache = $options['cache'];
+ $key = is_string($cache['key']) ? $cache['key'] : md5(serialize($options));
+ $data = S($key, '', $cache);
+ if (false !== $data) {
+ return $data;
+ }
+ }
+ $resultSet = $this->db->select($options);
+ if (false === $resultSet) {
+ return false;
+ }
+ if (empty($resultSet)) {
+ // 查询结果为空
+ return null;
+ }
+
+ if (is_string($resultSet)) {
+ return $resultSet;
+ }
+
+ $resultSet = array_map(array($this, '_read_data'), $resultSet);
+ $this->_after_select($resultSet, $options);
+ if (isset($options['index'])) {
+ // 对数据集进行索引
+ $index = explode(',', $options['index']);
+ foreach ($resultSet as $result) {
+ $_key = $result[$index[0]];
+ if (isset($index[1]) && isset($result[$index[1]])) {
+ $cols[$_key] = $result[$index[1]];
+ } else {
+ $cols[$_key] = $result;
+ }
+ }
+ $resultSet = $cols;
+ }
+ if (isset($cache)) {
+ S($key, $resultSet, $cache);
+ }
+ return $resultSet;
+ }
+ // 查询成功后的回调方法
+ protected function _after_select(&$resultSet, $options)
+ {}
+
+ /**
+ * 分析表达式
+ * @access protected
+ * @param array $options 表达式参数
+ * @return array
+ */
+ protected function _parseOptions($options = array())
+ {
+ if (is_array($options)) {
+ $options = array_merge($this->options, $options);
+ }
+
+ if (!isset($options['table'])) {
+ // 自动获取表名
+ $options['table'] = $this->getTableName();
+ $fields = $this->fields;
+ } else {
+ // 指定数据表 则重新获取字段列表 但不支持类型检测
+ $fields = $this->getDbFields();
+ }
+
+ // 数据表别名
+ if (!empty($options['alias'])) {
+ $options['table'] .= ' ' . $options['alias'];
+ }
+ // 记录操作的模型名称
+ $options['model'] = $this->name;
+
+ // 字段类型验证
+ if (isset($options['where']) && is_array($options['where']) && !empty($fields) && !isset($options['join'])) {
+ // 对数组查询条件进行字段类型检查
+ foreach ($options['where'] as $key => $val) {
+ $key = trim($key);
+ if (in_array($key, $fields, true)) {
+ if (is_scalar($val)) {
+ $this->_parseType($options['where'], $key);
+ }
+ } elseif (!is_numeric($key) && '_' != substr($key, 0, 1) && false === strpos($key, '.') && false === strpos($key, '(') && false === strpos($key, '|') && false === strpos($key, '&')) {
+ if (!empty($this->options['strict'])) {
+ E(L('_ERROR_QUERY_EXPRESS_') . ':[' . $key . '=>' . $val . ']');
+ }
+ unset($options['where'][$key]);
+ }
+ }
+ }
+ // 查询过后清空sql表达式组装 避免影响下次查询
+ $this->options = array();
+ // 表达式过滤
+ $this->_options_filter($options);
+ return $options;
+ }
+ // 表达式过滤回调方法
+ protected function _options_filter(&$options)
+ {}
+
+ /**
+ * 数据类型检测
+ * @access protected
+ * @param mixed $data 数据
+ * @param string $key 字段名
+ * @return void
+ */
+ protected function _parseType(&$data, $key)
+ {
+ if (!isset($this->options['bind'][':' . $key]) && isset($this->fields['_type'][$key])) {
+ $fieldType = strtolower($this->fields['_type'][$key]);
+ if (false !== strpos($fieldType, 'enum')) {
+ // 支持ENUM类型优先检测
+ } elseif (false === strpos($fieldType, 'bigint') && false !== strpos($fieldType, 'int')) {
+ $data[$key] = intval($data[$key]);
+ } elseif (false !== strpos($fieldType, 'float') || false !== strpos($fieldType, 'double')) {
+ $data[$key] = floatval($data[$key]);
+ } elseif (false !== strpos($fieldType, 'bool')) {
+ $data[$key] = (bool) $data[$key];
+ }
+ }
+ }
+
+ /**
+ * 数据读取后的处理
+ * @access protected
+ * @param array $data 当前数据
+ * @return array
+ */
+ protected function _read_data($data)
+ {
+ // 检查字段映射
+ if (!empty($this->_map) && C('READ_DATA_MAP')) {
+ foreach ($this->_map as $key => $val) {
+ if (isset($data[$val])) {
+ $data[$key] = $data[$val];
+ unset($data[$val]);
+ }
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * 查询数据
+ * @access public
+ * @param mixed $options 表达式参数
+ * @return mixed
+ */
+ public function find($options = array())
+ {
+ if (is_numeric($options) || is_string($options)) {
+ $where[$this->getPk()] = $options;
+ $options = array();
+ $options['where'] = $where;
+ }
+ // 根据复合主键查找记录
+ $pk = $this->getPk();
+ if (is_array($options) && (count($options) > 0) && is_array($pk)) {
+ // 根据复合主键查询
+ $count = 0;
+ foreach (array_keys($options) as $key) {
+ if (is_int($key)) {
+ $count++;
+ }
+
+ }
+ if (count($pk) == $count) {
+ $i = 0;
+ foreach ($pk as $field) {
+ $where[$field] = $options[$i];
+ unset($options[$i++]);
+ }
+ $options['where'] = $where;
+ } else {
+ return false;
+ }
+ }
+ // 总是查找一条记录
+ $options['limit'] = 1;
+ // 分析表达式
+ $options = $this->_parseOptions($options);
+ // 判断查询缓存
+ if (isset($options['cache'])) {
+ $cache = $options['cache'];
+ $key = is_string($cache['key']) ? $cache['key'] : md5(serialize($options));
+ $data = S($key, '', $cache);
+ if (false !== $data) {
+ $this->data = $data;
+ return $data;
+ }
+ }
+ $resultSet = $this->db->select($options);
+ if (false === $resultSet) {
+ return false;
+ }
+ if (empty($resultSet)) {
+// 查询结果为空
+ return null;
+ }
+ if (is_string($resultSet)) {
+ return $resultSet;
+ }
+
+ // 读取数据后的处理
+ $data = $this->_read_data($resultSet[0]);
+ $this->_after_find($data, $options);
+ $this->data = $data;
+ if (isset($cache)) {
+ S($key, $data, $cache);
+ }
+ return $this->data;
+ }
+ // 查询成功的回调方法
+ protected function _after_find(&$result, $options)
+ {}
+
+ /**
+ * 设置记录的某个字段值
+ * 支持使用数据库字段和方法
+ * @access public
+ * @param string|array $field 字段名
+ * @param string $value 字段值
+ * @return boolean
+ */
+ public function setField($field, $value = '')
+ {
+ if (is_array($field)) {
+ $data = $field;
+ } else {
+ $data[$field] = $value;
+ }
+ return $this->save($data);
+ }
+
+ /**
+ * 字段值增长
+ * @access public
+ * @param string $field 字段名
+ * @param integer $step 增长值
+ * @param integer $lazyTime 延时时间(s)
+ * @return boolean
+ */
+ public function setInc($field, $step = 1)
+ {
+ return $this->setField($field, array('exp', $field . '+' . $step));
+ }
+
+ /**
+ * 字段值减少
+ * @access public
+ * @param string $field 字段名
+ * @param integer $step 减少值
+ * @param integer $lazyTime 延时时间(s)
+ * @return boolean
+ */
+ public function setDec($field, $step = 1)
+ {
+ return $this->setField($field, array('exp', $field . '-' . $step));
+ }
+
+ /**
+ * 获取一条记录的某个字段值
+ * @access public
+ * @param string $field 字段名
+ * @param string $spea 字段数据间隔符号 NULL返回数组
+ * @return mixed
+ */
+ public function getField($field, $sepa = null)
+ {
+ $options['field'] = $field;
+ $options = $this->_parseOptions($options);
+ // 判断查询缓存
+ if (isset($options['cache'])) {
+ $cache = $options['cache'];
+ $key = is_string($cache['key']) ? $cache['key'] : md5($sepa . serialize($options));
+ $data = S($key, '', $cache);
+ if (false !== $data) {
+ return $data;
+ }
+ }
+ $field = trim($field);
+ if (strpos($field, ',') && false !== $sepa) {
+ // 多字段
+ if (!isset($options['limit'])) {
+ $options['limit'] = is_numeric($sepa) ? $sepa : '';
+ }
+ $resultSet = $this->db->select($options);
+ if (!empty($resultSet)) {
+ $_field = explode(',', $field);
+ $field = array_keys($resultSet[0]);
+ $key1 = array_shift($field);
+ $key2 = array_shift($field);
+ $cols = array();
+ $count = count($_field);
+ foreach ($resultSet as $result) {
+ $name = $result[$key1];
+ if (2 == $count) {
+ $cols[$name] = $result[$key2];
+ } else {
+ $cols[$name] = is_string($sepa) ? implode($sepa, array_slice($result, 1)) : $result;
+ }
+ }
+ if (isset($cache)) {
+ S($key, $cols, $cache);
+ }
+ return $cols;
+ }
+ } else {
+ // 查找一条记录
+ // 返回数据个数
+ if (true !== $sepa) { // 当sepa指定为true的时候 返回所有数据
+ $options['limit'] = is_numeric($sepa) ? $sepa : 1;
+ }
+ $result = $this->db->select($options);
+ if (!empty($result)) {
+ if (true !== $sepa && 1 == $options['limit']) {
+ $data = reset($result[0]);
+ if (isset($cache)) {
+ S($key, $data, $cache);
+ }
+ return $data;
+ }
+ foreach ($result as $val) {
+ $array[] = $val[$field];
+ }
+ if (isset($cache)) {
+ S($key, $array, $cache);
+ }
+ return $array;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 创建数据对象 但不保存到数据库
+ * @access public
+ * @param mixed $data 创建数据
+ * @return mixed
+ */
+ public function create($data = '')
+ {
+ // 如果没有传值默认取POST数据
+ if (empty($data)) {
+ $data = I('post.');
+ } elseif (is_object($data)) {
+ $data = get_object_vars($data);
+ }
+ // 验证数据
+ if (empty($data) || !is_array($data)) {
+ $this->error = L('_DATA_TYPE_INVALID_');
+ return false;
+ }
+
+ // 检测提交字段的合法性
+ if (isset($this->options['field'])) {
+ // $this->field('field1,field2...')->create()
+ $fields = $this->options['field'];
+ unset($this->options['field']);
+ }
+ if (isset($fields)) {
+ if (is_string($fields)) {
+ $fields = explode(',', $fields);
+ }
+ }
+
+ // 验证完成生成数据对象
+ if ($this->autoCheckFields) {
+ // 开启字段检测 则过滤非法字段数据
+ $fields = $this->getDbFields();
+ foreach ($data as $key => $val) {
+ if (!in_array($key, $fields)) {
+ unset($data[$key]);
+ } elseif (MAGIC_QUOTES_GPC && is_string($val)) {
+ $data[$key] = stripslashes($val);
+ }
+ }
+ }
+
+ // 赋值当前数据对象
+ $this->data = $data;
+ // 返回创建的数据以供其他调用
+ return $data;
+ }
+
+ /**
+ * 使用正则验证数据
+ * @access public
+ * @param string $value 要验证的数据
+ * @param string $rule 验证规则
+ * @return boolean
+ */
+ public function regex($value, $rule)
+ {
+ $validate = array(
+ 'require' => '/\S+/',
+ 'email' => '/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/',
+ 'url' => '/^http(s?):\/\/(?:[A-za-z0-9-]+\.)+[A-za-z]{2,4}(:\d+)?(?:[\/\?#][\/=\?%\-&~`@[\]\':+!\.#\w]*)?$/',
+ 'currency' => '/^\d+(\.\d+)?$/',
+ 'number' => '/^\d+$/',
+ 'zip' => '/^\d{6}$/',
+ 'integer' => '/^[-\+]?\d+$/',
+ 'double' => '/^[-\+]?\d+(\.\d+)?$/',
+ 'english' => '/^[A-Za-z]+$/',
+ );
+ // 检查是否有内置的正则表达式
+ if (isset($validate[strtolower($rule)])) {
+ $rule = $validate[strtolower($rule)];
+ }
+
+ return preg_match($rule, $value) === 1;
+ }
+
+ /**
+ * 验证数据 支持 in between equal length regex expire ip_allow ip_deny
+ * @access public
+ * @param string $value 验证数据
+ * @param mixed $rule 验证表达式
+ * @param string $type 验证方式 默认为正则验证
+ * @return boolean
+ */
+ public function check($value, $rule, $type = 'regex')
+ {
+ $type = strtolower(trim($type));
+ switch ($type) {
+ case 'in': // 验证是否在某个指定范围之内 逗号分隔字符串或者数组
+ case 'notin':
+ $range = is_array($rule) ? $rule : explode(',', $rule);
+ return 'in' == $type ? in_array($value, $range) : !in_array($value, $range);
+ case 'between': // 验证是否在某个范围
+ case 'notbetween': // 验证是否不在某个范围
+ if (is_array($rule)) {
+ $min = $rule[0];
+ $max = $rule[1];
+ } else {
+ list($min, $max) = explode(',', $rule);
+ }
+ return 'between' == $type ? $value >= $min && $value <= $max : $value < $min || $value > $max;
+ case 'equal': // 验证是否等于某个值
+ case 'notequal': // 验证是否等于某个值
+ return 'equal' == $type ? $value == $rule : $value != $rule;
+ case 'length': // 验证长度
+ $length = mb_strlen($value, 'utf-8'); // 当前数据长度
+ if (strpos($rule, ',')) {
+ // 长度区间
+ list($min, $max) = explode(',', $rule);
+ return $length >= $min && $length <= $max;
+ } else {
+// 指定长度
+ return $length == $rule;
+ }
+ case 'expire':
+ list($start, $end) = explode(',', $rule);
+ if (!is_numeric($start)) {
+ $start = strtotime($start);
+ }
+
+ if (!is_numeric($end)) {
+ $end = strtotime($end);
+ }
+
+ return NOW_TIME >= $start && NOW_TIME <= $end;
+ case 'ip_allow': // IP 操作许可验证
+ return in_array(get_client_ip(), explode(',', $rule));
+ case 'ip_deny': // IP 操作禁止验证
+ return !in_array(get_client_ip(), explode(',', $rule));
+ case 'regex':
+ default: // 默认使用正则验证 可以使用验证类中定义的验证名称
+ // 检查附加规则
+ return $this->regex($value, $rule);
+ }
+ }
+
+ /**
+ * SQL查询
+ * @access public
+ * @param string $sql SQL指令
+ * @return mixed
+ */
+ public function query($sql)
+ {
+ return $this->db->query($sql);
+ }
+
+ /**
+ * 执行SQL语句
+ * @access public
+ * @param string $sql SQL指令
+ * @return false | integer
+ */
+ public function execute($sql)
+ {
+ return $this->db->execute($sql);
+ }
+
+ /**
+ * 切换当前的数据库连接
+ * @access public
+ * @param integer $linkNum 连接序号
+ * @param mixed $config 数据库连接信息
+ * @param boolean $force 强制重新连接
+ * @return Model
+ */
+ public function db($linkNum = '', $config = '', $force = false)
+ {
+ if ('' === $linkNum && $this->db) {
+ return $this->db;
+ }
+
+ if (!isset($this->_db[$linkNum]) || $force) {
+ // 创建一个新的实例
+ if (!empty($config) && is_string($config) && false === strpos($config, '/')) {
+ // 支持读取配置参数
+ $config = C($config);
+ }
+ $this->_db[$linkNum] = Db::getInstance($config);
+ } elseif (null === $config) {
+ $this->_db[$linkNum]->close(); // 关闭数据库连接
+ unset($this->_db[$linkNum]);
+ return;
+ }
+
+ // 切换数据库连接
+ $this->db = $this->_db[$linkNum];
+ $this->_after_db();
+ // 字段检测
+ if (!empty($this->name) && $this->autoCheckFields) {
+ $this->_checkTableInfo();
+ }
+
+ return $this;
+ }
+ // 数据库切换后回调方法
+ protected function _after_db()
+ {}
+
+ /**
+ * 得到当前的数据对象名称
+ * @access public
+ * @return string
+ */
+ public function getModelName()
+ {
+ if (empty($this->name)) {
+ $name = substr(get_class($this), 0, -strlen(C('DEFAULT_M_LAYER')));
+ if ($pos = strrpos($name, '\\')) {
+//有命名空间
+ $this->name = substr($name, $pos + 1);
+ } else {
+ $this->name = $name;
+ }
+ }
+ return $this->name;
+ }
+
+ /**
+ * 得到完整的数据表名
+ * @access public
+ * @return string
+ */
+ public function getTableName()
+ {
+ if (empty($this->trueTableName)) {
+ $tableName = !empty($this->tablePrefix) ? $this->tablePrefix : '';
+ if (!empty($this->tableName)) {
+ $tableName .= $this->tableName;
+ } else {
+ $tableName .= parse_name($this->name);
+ }
+ $this->trueTableName = strtolower($tableName);
+ }
+ return (!empty($this->dbName) ? $this->dbName . '.' : '') . $this->trueTableName;
+ }
+
+ /**
+ * 启动事务
+ * @access public
+ * @return void
+ */
+ public function startTrans()
+ {
+ $this->commit();
+ $this->db->startTrans();
+ return;
+ }
+
+ /**
+ * 提交事务
+ * @access public
+ * @return boolean
+ */
+ public function commit()
+ {
+ return $this->db->commit();
+ }
+
+ /**
+ * 事务回滚
+ * @access public
+ * @return boolean
+ */
+ public function rollback()
+ {
+ return $this->db->rollback();
+ }
+
+ /**
+ * 返回模型的错误信息
+ * @access public
+ * @return string
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * 返回数据库的错误信息
+ * @access public
+ * @return string
+ */
+ public function getDbError()
+ {
+ return $this->db->getError();
+ }
+
+ /**
+ * 返回最后插入的ID
+ * @access public
+ * @return string
+ */
+ public function getLastInsID()
+ {
+ return $this->db->getLastInsID();
+ }
+
+ /**
+ * 返回最后执行的sql语句
+ * @access public
+ * @return string
+ */
+ public function getLastSql()
+ {
+ return $this->db->getLastSql($this->name);
+ }
+ // 鉴于getLastSql比较常用 增加_sql 别名
+ public function _sql()
+ {
+ return $this->getLastSql();
+ }
+
+ /**
+ * 获取主键名称
+ * @access public
+ * @return string
+ */
+ public function getPk()
+ {
+ return $this->pk;
+ }
+
+ /**
+ * 获取数据表字段信息
+ * @access public
+ * @return array
+ */
+ public function getDbFields()
+ {
+ if (isset($this->options['table'])) {
+// 动态指定表名
+ if (is_array($this->options['table'])) {
+ $table = key($this->options['table']);
+ } else {
+ $table = $this->options['table'];
+ }
+ $fields = $this->db->getFields($table);
+ return $fields ? array_keys($fields) : false;
+ }
+ if ($this->fields) {
+ $fields = $this->fields;
+ unset($fields['_type'], $fields['_pk']);
+ return $fields;
+ }
+ return false;
+ }
+
+ /**
+ * 设置数据对象值
+ * @access public
+ * @param mixed $data 数据
+ * @return Model
+ */
+ public function data($data = '')
+ {
+ if ('' === $data && !empty($this->data)) {
+ return $this->data;
+ }
+ if (is_object($data)) {
+ $data = get_object_vars($data);
+ } elseif (is_string($data)) {
+ parse_str($data, $data);
+ } elseif (!is_array($data)) {
+ E(L('_DATA_TYPE_INVALID_'));
+ }
+ $this->data = $data;
+ return $this;
+ }
+
+ /**
+ * 指定当前的数据表
+ * @access public
+ * @param mixed $table
+ * @return Model
+ */
+ public function table($table)
+ {
+ $prefix = $this->tablePrefix;
+ if (is_array($table)) {
+ $this->options['table'] = $table;
+ } elseif (!empty($table)) {
+ //将__TABLE_NAME__替换成带前缀的表名
+ $table = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) {return $prefix . strtolower($match[1]);}, $table);
+ $this->options['table'] = $table;
+ }
+ return $this;
+ }
+
+ /**
+ * USING支持 用于多表删除
+ * @access public
+ * @param mixed $using
+ * @return Model
+ */
+ public function using($using)
+ {
+ $prefix = $this->tablePrefix;
+ if (is_array($using)) {
+ $this->options['using'] = $using;
+ } elseif (!empty($using)) {
+ //将__TABLE_NAME__替换成带前缀的表名
+ $using = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) {return $prefix . strtolower($match[1]);}, $using);
+ $this->options['using'] = $using;
+ }
+ return $this;
+ }
+
+ /**
+ * 查询SQL组装 join
+ * @access public
+ * @param mixed $join
+ * @param string $type JOIN类型
+ * @return Model
+ */
+ public function join($join, $type = 'INNER')
+ {
+ $prefix = $this->tablePrefix;
+ if (is_array($join)) {
+ foreach ($join as $key => &$_join) {
+ $_join = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) {return $prefix . strtolower($match[1]);}, $_join);
+ $_join = false !== stripos($_join, 'JOIN') ? $_join : $type . ' JOIN ' . $_join;
+ }
+ $this->options['join'] = $join;
+ } elseif (!empty($join)) {
+ //将__TABLE_NAME__字符串替换成带前缀的表名
+ $join = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) {return $prefix . strtolower($match[1]);}, $join);
+ $this->options['join'][] = false !== stripos($join, 'JOIN') ? $join : $type . ' JOIN ' . $join;
+ }
+ return $this;
+ }
+
+ /**
+ * 查询SQL组装 union
+ * @access public
+ * @param mixed $union
+ * @param boolean $all
+ * @return Model
+ */
+ public function union($union, $all = false)
+ {
+ if (empty($union)) {
+ return $this;
+ }
+
+ if ($all) {
+ $this->options['union']['_all'] = true;
+ }
+ if (is_object($union)) {
+ $union = get_object_vars($union);
+ }
+ // 转换union表达式
+ if (is_string($union)) {
+ $prefix = $this->tablePrefix;
+ //将__TABLE_NAME__字符串替换成带前缀的表名
+ $options = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) {return $prefix . strtolower($match[1]);}, $union);
+ } elseif (is_array($union)) {
+ if (isset($union[0])) {
+ $this->options['union'] = array_merge($this->options['union'], $union);
+ return $this;
+ } else {
+ $options = $union;
+ }
+ } else {
+ E(L('_DATA_TYPE_INVALID_'));
+ }
+ $this->options['union'][] = $options;
+ return $this;
+ }
+
+ /**
+ * 查询缓存
+ * @access public
+ * @param mixed $key
+ * @param integer $expire
+ * @param string $type
+ * @return Model
+ */
+ public function cache($key = true, $expire = null, $type = '')
+ {
+ // 增加快捷调用方式 cache(10) 等同于 cache(true, 10)
+ if (is_numeric($key) && is_null($expire)) {
+ $expire = $key;
+ $key = true;
+ }
+ if (false !== $key) {
+ $this->options['cache'] = array('key' => $key, 'expire' => $expire, 'type' => $type);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 指定查询字段 支持字段排除
+ * @access public
+ * @param mixed $field
+ * @param boolean $except 是否排除
+ * @return Model
+ */
+ public function field($field, $except = false)
+ {
+ if (true === $field) {
+// 获取全部字段
+ $fields = $this->getDbFields();
+ $field = $fields ?: '*';
+ } elseif ($except) {
+// 字段排除
+ if (is_string($field)) {
+ $field = explode(',', $field);
+ }
+ $fields = $this->getDbFields();
+ $field = $fields ? array_diff($fields, $field) : $field;
+ }
+ $this->options['field'] = $field;
+ return $this;
+ }
+
+ /**
+ * 调用命名范围
+ * @access public
+ * @param mixed $scope 命名范围名称 支持多个 和直接定义
+ * @param array $args 参数
+ * @return Model
+ */
+ public function scope($scope = '', $args = null)
+ {
+ if ('' === $scope) {
+ if (isset($this->_scope['default'])) {
+ // 默认的命名范围
+ $options = $this->_scope['default'];
+ } else {
+ return $this;
+ }
+ } elseif (is_string($scope)) {
+ // 支持多个命名范围调用 用逗号分割
+ $scopes = explode(',', $scope);
+ $options = array();
+ foreach ($scopes as $name) {
+ if (!isset($this->_scope[$name])) {
+ continue;
+ }
+
+ $options = array_merge($options, $this->_scope[$name]);
+ }
+ if (!empty($args) && is_array($args)) {
+ $options = array_merge($options, $args);
+ }
+ } elseif (is_array($scope)) {
+ // 直接传入命名范围定义
+ $options = $scope;
+ }
+
+ if (is_array($options) && !empty($options)) {
+ $this->options = array_merge($this->options, array_change_key_case($options));
+ }
+ return $this;
+ }
+
+ /**
+ * 指定查询条件 支持安全过滤
+ * @access public
+ * @param mixed $where 条件表达式
+ * @param mixed $parse 预处理参数
+ * @return Model
+ */
+ public function where($where, $parse = null)
+ {
+ if (!is_null($parse) && is_string($where)) {
+ if (!is_array($parse)) {
+ $parse = func_get_args();
+ array_shift($parse);
+ }
+ $parse = array_map(array($this->db, 'escapeString'), $parse);
+ $where = vsprintf($where, $parse);
+ } elseif (is_object($where)) {
+ $where = get_object_vars($where);
+ }
+ if (is_string($where) && '' != $where) {
+ $map = array();
+ $map['_string'] = $where;
+ $where = $map;
+ }
+ if (isset($this->options['where'])) {
+ $this->options['where'] = array_merge($this->options['where'], $where);
+ } else {
+ $this->options['where'] = $where;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 指定查询数量
+ * @access public
+ * @param mixed $offset 起始位置
+ * @param mixed $length 查询数量
+ * @return Model
+ */
+ public function limit($offset, $length = null)
+ {
+ if (is_null($length) && strpos($offset, ',')) {
+ list($offset, $length) = explode(',', $offset);
+ }
+ $this->options['limit'] = intval($offset) . ($length ? ',' . intval($length) : '');
+ return $this;
+ }
+
+ /**
+ * 指定分页
+ * @access public
+ * @param mixed $page 页数
+ * @param mixed $listRows 每页数量
+ * @return Model
+ */
+ public function page($page, $listRows = null)
+ {
+ if (is_null($listRows) && strpos($page, ',')) {
+ list($page, $listRows) = explode(',', $page);
+ }
+ $this->options['page'] = array(intval($page), intval($listRows));
+ return $this;
+ }
+
+ /**
+ * 查询注释
+ * @access public
+ * @param string $comment 注释
+ * @return Model
+ */
+ public function comment($comment)
+ {
+ $this->options['comment'] = $comment;
+ return $this;
+ }
+
+ /**
+ * 获取执行的SQL语句
+ * @access public
+ * @param boolean $fetch 是否返回sql
+ * @return Model
+ */
+ public function fetchSql($fetch)
+ {
+ $this->options['fetch_sql'] = $fetch;
+ return $this;
+ }
+
+ /**
+ * 参数绑定
+ * @access public
+ * @param string $key 参数名
+ * @param mixed $value 绑定的变量及绑定参数
+ * @return Model
+ */
+ public function bind($key, $value = false)
+ {
+ if (is_array($key)) {
+ $this->options['bind'] = $key;
+ } else {
+ $num = func_num_args();
+ if ($num > 2) {
+ $params = func_get_args();
+ array_shift($params);
+ $this->options['bind'][$key] = $params;
+ } else {
+ $this->options['bind'][$key] = $value;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * 设置模型的属性值
+ * @access public
+ * @param string $name 名称
+ * @param mixed $value 值
+ * @return Model
+ */
+ public function setProperty($name, $value)
+ {
+ if (property_exists($this, $name)) {
+ $this->$name = $value;
+ }
+
+ return $this;
+ }
+
+}
diff --git a/Framework/Mode/Lite/View.class.php b/Framework/Mode/Lite/View.class.php
new file mode 100644
index 00000000..31eb7a20
--- /dev/null
+++ b/Framework/Mode/Lite/View.class.php
@@ -0,0 +1,325 @@
+
+// +----------------------------------------------------------------------
+namespace Think;
+
+/**
+ * ThinkPHP 视图类
+ */
+class View
+{
+ /**
+ * 模板输出变量
+ * @var tVar
+ * @access protected
+ */
+ protected $tVar = array();
+
+ /**
+ * 模板主题
+ * @var theme
+ * @access protected
+ */
+ protected $theme = '';
+
+ /**
+ * 模板变量赋值
+ * @access public
+ * @param mixed $name
+ * @param mixed $value
+ */
+ public function assign($name, $value = '')
+ {
+ if (is_array($name)) {
+ $this->tVar = array_merge($this->tVar, $name);
+ } else {
+ $this->tVar[$name] = $value;
+ }
+ }
+
+ /**
+ * 取得模板变量的值
+ * @access public
+ * @param string $name
+ * @return mixed
+ */
+ public function get($name = '')
+ {
+ if ('' === $name) {
+ return $this->tVar;
+ }
+ return isset($this->tVar[$name]) ? $this->tVar[$name] : false;
+ }
+
+ /**
+ * 加载模板和页面输出 可以返回输出内容
+ * @access public
+ * @param string $templateFile 模板文件名
+ * @param string $charset 模板输出字符集
+ * @param string $contentType 输出类型
+ * @param string $content 模板输出内容
+ * @param string $prefix 模板缓存前缀
+ * @return mixed
+ */
+ public function display($templateFile = '', $charset = '', $contentType = '', $content = '', $prefix = '')
+ {
+ G('viewStartTime');
+ // 解析并获取模板内容
+ $content = $this->fetch($templateFile, $content, $prefix);
+ // 输出模板内容
+ $this->render($content, $charset, $contentType);
+ }
+
+ /**
+ * 输出内容文本可以包括Html
+ * @access private
+ * @param string $content 输出内容
+ * @param string $charset 模板输出字符集
+ * @param string $contentType 输出类型
+ * @return mixed
+ */
+ private function render($content, $charset = '', $contentType = '')
+ {
+ if (empty($charset)) {
+ $charset = C('DEFAULT_CHARSET');
+ }
+
+ if (empty($contentType)) {
+ $contentType = C('TMPL_CONTENT_TYPE');
+ }
+
+ // 网页字符编码
+ header('Content-Type:' . $contentType . '; charset=' . $charset);
+ header('Cache-control: ' . C('HTTP_CACHE_CONTROL')); // 页面缓存控制
+ header('X-Powered-By:OpenCMF');
+ // 输出模板文件
+ echo $content;
+ }
+
+ /**
+ * 解析和获取模板内容 用于输出
+ * @access public
+ * @param string $templateFile 模板文件名
+ * @param string $content 模板输出内容
+ * @param string $prefix 模板缓存前缀
+ * @return string
+ */
+ public function fetch($templateFile = '', $content = '', $prefix = '')
+ {
+ if (empty($content)) {
+ $templateFile = $this->parseTemplate($templateFile);
+ // 模板文件不存在直接返回
+ if (!is_file($templateFile)) {
+ E(L('_TEMPLATE_NOT_EXIST_') . ':' . $templateFile);
+ }
+
+ } else {
+ defined('THEME_PATH') or define('THEME_PATH', $this->getThemePath());
+ }
+ // 页面缓存
+ ob_start();
+ ob_implicit_flush(0);
+ if ('php' == strtolower(C('TMPL_ENGINE_TYPE'))) {
+ // 使用PHP原生模板
+ $_content = $content;
+ // 模板阵列变量分解成为独立变量
+ extract($this->tVar, EXTR_OVERWRITE);
+ // 直接载入PHP模板
+ empty($_content) ? include $templateFile : eval('?>' . $_content);
+ } else {
+ // 视图解析标签
+ $params = array('var' => $this->tVar, 'file' => $templateFile, 'content' => $content, 'prefix' => $prefix);
+ $_content = !empty($content) ?: $templateFile;
+ if ((!empty($content) && $this->checkContentCache($content, $prefix)) || $this->checkCache($templateFile, $prefix)) {
+ // 缓存有效
+ //载入模版缓存文件
+ Storage::load(C('CACHE_PATH') . $prefix . md5($_content) . C('TMPL_CACHFILE_SUFFIX'), $this->tVar);
+ } else {
+ $tpl = Think::instance('Think\\Template');
+ // 编译并加载模板文件
+ $tpl->fetch($_content, $this->tVar, $prefix);
+ }
+ }
+ // 获取并清空缓存
+ $content = ob_get_clean();
+ // 内容过滤标签
+ // 系统默认的特殊变量替换
+ $replace = array(
+ '__ROOT__' => __ROOT__, // 当前网站地址
+ '__APP__' => __APP__, // 当前应用地址
+ '__MODULE__' => __MODULE__,
+ '__ACTION__' => __ACTION__, // 当前操作地址
+ '__SELF__' => __SELF__, // 当前页面地址
+ '__CONTROLLER__' => __CONTROLLER__,
+ '__URL__' => __CONTROLLER__,
+ '__PUBLIC__' => __ROOT__ . '/Public', // 站点公共目录
+ );
+ // 允许用户自定义模板的字符串替换
+ if (is_array(C('TMPL_PARSE_STRING'))) {
+ $replace = array_merge($replace, C('TMPL_PARSE_STRING'));
+ }
+
+ $content = str_replace(array_keys($replace), array_values($replace), $content);
+ // 输出模板文件
+ return $content;
+ }
+
+ /**
+ * 检查缓存文件是否有效
+ * 如果无效则需要重新编译
+ * @access public
+ * @param string $tmplTemplateFile 模板文件名
+ * @return boolean
+ */
+ protected function checkCache($tmplTemplateFile, $prefix = '')
+ {
+ if (!C('TMPL_CACHE_ON')) // 优先对配置设定检测
+ {
+ return false;
+ }
+
+ $tmplCacheFile = C('CACHE_PATH') . $prefix . md5($tmplTemplateFile) . C('TMPL_CACHFILE_SUFFIX');
+ if (!Storage::has($tmplCacheFile)) {
+ return false;
+ } elseif (filemtime($tmplTemplateFile) > Storage::get($tmplCacheFile, 'mtime')) {
+ // 模板文件如果有更新则缓存需要更新
+ return false;
+ } elseif (C('TMPL_CACHE_TIME') != 0 && time() > Storage::get($tmplCacheFile, 'mtime') + C('TMPL_CACHE_TIME')) {
+ // 缓存是否在有效期
+ return false;
+ }
+ // 开启布局模板
+ if (C('LAYOUT_ON')) {
+ $layoutFile = THEME_PATH . C('LAYOUT_NAME') . C('TMPL_TEMPLATE_SUFFIX');
+ if (filemtime($layoutFile) > Storage::get($tmplCacheFile, 'mtime')) {
+ return false;
+ }
+ }
+ // 缓存有效
+ return true;
+ }
+
+ /**
+ * 检查缓存内容是否有效
+ * 如果无效则需要重新编译
+ * @access public
+ * @param string $tmplContent 模板内容
+ * @return boolean
+ */
+ protected function checkContentCache($tmplContent, $prefix = '')
+ {
+ if (Storage::has(C('CACHE_PATH') . $prefix . md5($tmplContent) . C('TMPL_CACHFILE_SUFFIX'))) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * 自动定位模板文件
+ * @access protected
+ * @param string $template 模板文件规则
+ * @return string
+ */
+ public function parseTemplate($template = '')
+ {
+ if (is_file($template)) {
+ return $template;
+ }
+ $depr = C('TMPL_FILE_DEPR');
+ $template = str_replace(':', $depr, $template);
+
+ // 获取当前模块
+ $module = MODULE_NAME;
+ if (strpos($template, '@')) {
+ // 跨模块调用模版文件
+ list($module, $template) = explode('@', $template);
+ }
+ // 获取当前主题的模版路径
+ defined('THEME_PATH') or define('THEME_PATH', $this->getThemePath($module));
+
+ // 分析模板文件规则
+ if ('' == $template) {
+ // 如果模板文件名为空 按照默认规则定位
+ $template = CONTROLLER_NAME . $depr . ACTION_NAME;
+ } elseif (false === strpos($template, $depr)) {
+ $template = CONTROLLER_NAME . $depr . $template;
+ }
+ $file = THEME_PATH . $template . C('TMPL_TEMPLATE_SUFFIX');
+ if (C('TMPL_LOAD_DEFAULTTHEME') && THEME_NAME != C('DEFAULT_THEME') && !is_file($file)) {
+ // 找不到当前主题模板的时候定位默认主题中的模板
+ $file = dirname(THEME_PATH) . '/' . C('DEFAULT_THEME') . '/' . $template . C('TMPL_TEMPLATE_SUFFIX');
+ }
+ return $file;
+ }
+
+ /**
+ * 获取当前的模板路径
+ * @access protected
+ * @param string $module 模块名
+ * @return string
+ */
+ protected function getThemePath($module = MODULE_NAME)
+ {
+ // 获取当前主题名称
+ $theme = $this->getTemplateTheme();
+ // 获取当前主题的模版路径
+ $tmplPath = C('VIEW_PATH'); // 模块设置独立的视图目录
+ if (!$tmplPath) {
+ // 定义TMPL_PATH 则改变全局的视图目录到模块之外
+ $tmplPath = defined('TMPL_PATH') ? TMPL_PATH . $module . '/' : APP_PATH . $module . '/' . C('DEFAULT_V_LAYER') . '/';
+ }
+ return $tmplPath . $theme;
+ }
+
+ /**
+ * 设置当前输出的模板主题
+ * @access public
+ * @param mixed $theme 主题名称
+ * @return View
+ */
+ public function theme($theme)
+ {
+ $this->theme = $theme;
+ return $this;
+ }
+
+ /**
+ * 获取当前的模板主题
+ * @access private
+ * @return string
+ */
+ private function getTemplateTheme()
+ {
+ if ($this->theme) {
+ // 指定模板主题
+ $theme = $this->theme;
+ } else {
+ /* 获取模板主题名称 */
+ $theme = C('DEFAULT_THEME');
+ if (C('TMPL_DETECT_THEME')) {
+// 自动侦测模板主题
+ $t = C('VAR_TEMPLATE');
+ if (isset($_GET[$t])) {
+ $theme = $_GET[$t];
+ } elseif (cookie('think_template')) {
+ $theme = cookie('think_template');
+ }
+ if (!in_array($theme, explode(',', C('THEME_LIST')))) {
+ $theme = C('DEFAULT_THEME');
+ }
+ cookie('think_template', $theme, 864000);
+ }
+ }
+ defined('THEME_NAME') || define('THEME_NAME', $theme); // 当前模板主题名称
+ return $theme ? $theme . '/' : '';
+ }
+
+}
diff --git a/Framework/Mode/Lite/convention.php b/Framework/Mode/Lite/convention.php
new file mode 100644
index 00000000..cd9b1a8c
--- /dev/null
+++ b/Framework/Mode/Lite/convention.php
@@ -0,0 +1,162 @@
+
+// +----------------------------------------------------------------------
+
+/**
+ * ThinkPHP惯例配置文件
+ * 该文件请不要修改,如果要覆盖惯例配置的值,可在应用配置文件中设定和惯例不符的配置项
+ * 配置名称大小写任意,系统会统一转换成小写
+ * 所有配置参数都可以在生效前动态改变
+ */
+return array(
+ /* 应用设定 */
+ 'APP_SUB_DOMAIN_DEPLOY' => false, // 是否开启子域名部署
+ 'APP_SUB_DOMAIN_RULES' => array(), // 子域名部署规则
+ 'APP_DOMAIN_SUFFIX' => '', // 域名后缀 如果是com.cn net.cn 之类的后缀必须设置
+ 'ACTION_SUFFIX' => '', // 操作方法后缀
+ 'MULTI_MODULE' => true, // 是否允许多模块 如果为false 则必须设置 DEFAULT_MODULE
+ 'MODULE_DENY_LIST' => array('Common', 'Runtime'),
+
+ /* Cookie设置 */
+ 'COOKIE_EXPIRE' => 0, // Cookie有效期
+ 'COOKIE_DOMAIN' => '', // Cookie有效域名
+ 'COOKIE_PATH' => '/', // Cookie路径
+ 'COOKIE_PREFIX' => '', // Cookie前缀 避免冲突
+ 'COOKIE_SECURE' => false, // Cookie安全传输
+ 'COOKIE_HTTPONLY' => '', // Cookie httponly设置
+
+ /* 默认设定 */
+ 'DEFAULT_M_LAYER' => 'Model', // 默认的模型层名称
+ 'DEFAULT_C_LAYER' => 'Controller', // 默认的控制器层名称
+ 'DEFAULT_V_LAYER' => 'View', // 默认的视图层名称
+ 'DEFAULT_LANG' => 'zh-cn', // 默认语言
+ 'DEFAULT_THEME' => '', // 默认模板主题名称
+ 'DEFAULT_MODULE' => 'Home', // 默认模块
+ 'DEFAULT_CONTROLLER' => 'Index', // 默认控制器名称
+ 'DEFAULT_ACTION' => 'index', // 默认操作名称
+ 'DEFAULT_CHARSET' => 'utf-8', // 默认输出编码
+ 'DEFAULT_TIMEZONE' => 'PRC', // 默认时区
+ 'DEFAULT_AJAX_RETURN' => 'JSON', // 默认AJAX 数据返回格式,可选JSON XML ...
+ 'DEFAULT_JSONP_HANDLER' => 'jsonpReturn', // 默认JSONP格式返回的处理方法
+ 'DEFAULT_FILTER' => 'htmlspecialchars', // 默认参数过滤方法 用于I函数...
+
+ /* 数据库设置 */
+ 'DB_TYPE' => '', // 数据库类型
+ 'DB_HOST' => '', // 服务器地址
+ 'DB_NAME' => '', // 数据库名
+ 'DB_USER' => '', // 用户名
+ 'DB_PWD' => '', // 密码
+ 'DB_PORT' => '', // 端口
+ 'DB_PREFIX' => '', // 数据库表前缀
+ 'DB_PARAMS' => array(), // 数据库连接参数
+ 'DB_DEBUG' => true, // 数据库调试模式 开启后可以记录SQL日志
+ 'DB_FIELDS_CACHE' => true, // 启用字段缓存
+ 'DB_CHARSET' => 'utf8', // 数据库编码默认采用utf8
+ 'DB_DEPLOY_TYPE' => 0, // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
+ 'DB_RW_SEPARATE' => false, // 数据库读写是否分离 主从式有效
+ 'DB_MASTER_NUM' => 1, // 读写分离后 主服务器数量
+ 'DB_SLAVE_NO' => '', // 指定从服务器序号
+
+ /* 数据缓存设置 */
+ 'DATA_CACHE_TIME' => 0, // 数据缓存有效期 0表示永久缓存
+ 'DATA_CACHE_COMPRESS' => false, // 数据缓存是否压缩缓存
+ 'DATA_CACHE_CHECK' => false, // 数据缓存是否校验缓存
+ 'DATA_CACHE_PREFIX' => '', // 缓存前缀
+ 'DATA_CACHE_TYPE' => 'File', // 数据缓存类型,支持:File|Db|Apc|Memcache|Shmop|Sqlite|Xcache|Apachenote|Eaccelerator
+ 'DATA_CACHE_PATH' => TEMP_PATH, // 缓存路径设置 (仅对File方式缓存有效)
+ 'DATA_CACHE_KEY' => '', // 缓存文件KEY (仅对File方式缓存有效)
+ 'DATA_CACHE_SUBDIR' => false, // 使用子目录缓存 (自动根据缓存标识的哈希创建子目录)
+ 'DATA_PATH_LEVEL' => 1, // 子目录缓存级别
+
+ /* 错误设置 */
+ 'ERROR_MESSAGE' => '页面错误!请稍后再试~', //错误显示信息,非调试模式有效
+ 'ERROR_PAGE' => '', // 错误定向页面
+ 'SHOW_ERROR_MSG' => false, // 显示错误信息
+ 'TRACE_MAX_RECORD' => 100, // 每个级别的错误信息 最大记录数
+
+ /* 日志设置 */
+ 'LOG_RECORD' => false, // 默认不记录日志
+ 'LOG_TYPE' => 'File', // 日志记录类型 默认为文件方式
+ 'LOG_LEVEL' => 'EMERG,ALERT,CRIT,ERR', // 允许记录的日志级别
+ 'LOG_FILE_SIZE' => 2097152, // 日志文件大小限制
+ 'LOG_EXCEPTION_RECORD' => false, // 是否记录异常信息日志
+
+ /* SESSION设置 */
+ 'SESSION_AUTO_START' => false, // 是否自动开启Session
+ 'SESSION_OPTIONS' => array(), // session 配置数组 支持type name id path expire domain 等参数
+ 'SESSION_TYPE' => '', // session hander类型 默认无需设置 除非扩展了session hander驱动
+ 'SESSION_PREFIX' => '', // session 前缀
+ //'VAR_SESSION_ID' => 'session_id', //sessionID的提交变量
+
+ /* 模板引擎设置 */
+ 'TMPL_CONTENT_TYPE' => 'text/html', // 默认模板输出类型
+ 'TMPL_ACTION_ERROR' => THINK_PATH . 'Tpl/dispatch_jump.tpl', // 默认错误跳转对应的模板文件
+ 'TMPL_ACTION_SUCCESS' => THINK_PATH . 'Tpl/dispatch_jump.tpl', // 默认成功跳转对应的模板文件
+ 'TMPL_EXCEPTION_FILE' => THINK_PATH . 'Tpl/think_exception.tpl', // 异常页面的模板文件
+ 'TMPL_DETECT_THEME' => false, // 自动侦测模板主题
+ 'TMPL_TEMPLATE_SUFFIX' => '.html', // 默认模板文件后缀
+ 'TMPL_FILE_DEPR' => '/', //模板文件CONTROLLER_NAME与ACTION_NAME之间的分割符
+ // 布局设置
+ 'TMPL_ENGINE_TYPE' => 'Think', // 默认模板引擎 以下设置仅对使用Think模板引擎有效
+ 'TMPL_CACHFILE_SUFFIX' => '.php', // 默认模板缓存后缀
+ 'TMPL_DENY_FUNC_LIST' => 'echo,exit', // 模板引擎禁用函数
+ 'TMPL_DENY_PHP' => false, // 默认模板引擎是否禁用PHP原生代码
+ 'TMPL_L_DELIM' => '{', // 模板引擎普通标签开始标记
+ 'TMPL_R_DELIM' => '}', // 模板引擎普通标签结束标记
+ 'TMPL_VAR_IDENTIFY' => 'array', // 模板变量识别。留空自动判断,参数为'obj'则表示对象
+ 'TMPL_STRIP_SPACE' => true, // 是否去除模板文件里面的html空格与换行
+ 'TMPL_CACHE_ON' => true, // 是否开启模板编译缓存,设为false则每次都会重新编译
+ 'TMPL_CACHE_PREFIX' => '', // 模板缓存前缀标识,可以动态改变
+ 'TMPL_CACHE_TIME' => 0, // 模板缓存有效期 0 为永久,(以数字为值,单位:秒)
+ 'TMPL_LAYOUT_ITEM' => '{__CONTENT__}', // 布局模板的内容替换标识
+ 'LAYOUT_ON' => false, // 是否启用布局
+ 'LAYOUT_NAME' => 'layout', // 当前布局名称 默认为layout
+
+ // Think模板引擎标签库相关设定
+ 'TAGLIB_BEGIN' => '<', // 标签库标签开始标记
+ 'TAGLIB_END' => '>', // 标签库标签结束标记
+ 'TAGLIB_LOAD' => true, // 是否使用内置标签库之外的其它标签库,默认自动检测
+ 'TAGLIB_BUILD_IN' => 'cx', // 内置标签库名称(标签使用不必指定标签库名称),以逗号分隔 注意解析顺序
+ 'TAGLIB_PRE_LOAD' => '', // 需要额外加载的标签库(须指定标签库名称),多个以逗号分隔
+
+ /* URL设置 */
+ 'URL_CASE_INSENSITIVE' => true, // 默认false 表示URL区分大小写 true则表示不区分大小写
+ 'URL_MODEL' => 1, // URL访问模式,可选参数0、1、2、3,代表以下四种模式:
+ // 0 (普通模式); 1 (PATHINFO 模式); 2 (REWRITE 模式); 3 (兼容模式) 默认为PATHINFO 模式
+ 'URL_PATHINFO_DEPR' => '/', // PATHINFO模式下,各参数之间的分割符号
+ 'URL_PATHINFO_FETCH' => 'ORIG_PATH_INFO,REDIRECT_PATH_INFO,REDIRECT_URL', // 用于兼容判断PATH_INFO 参数的SERVER替代变量列表
+ 'URL_REQUEST_URI' => 'REQUEST_URI', // 获取当前页面地址的系统变量 默认为REQUEST_URI
+ 'URL_HTML_SUFFIX' => 'html', // URL伪静态后缀设置
+ 'URL_DENY_SUFFIX' => 'ico|png|gif|jpg', // URL禁止访问的后缀设置
+ 'URL_PARAMS_BIND' => true, // URL变量绑定到Action方法参数
+ 'URL_PARAMS_BIND_TYPE' => 0, // URL变量绑定的类型 0 按变量名绑定 1 按变量顺序绑定
+ 'URL_PARAMS_FILTER' => false, // URL变量绑定过滤
+ 'URL_PARAMS_FILTER_TYPE' => '', // URL变量绑定过滤方法 如果为空 调用DEFAULT_FILTER
+ 'URL_ROUTER_ON' => false, // 是否开启URL路由
+ 'URL_ROUTE_RULES' => array(), // 默认路由规则 针对模块
+ 'URL_MAP_RULES' => array(), // URL映射定义规则
+
+ /* 系统变量名称设置 */
+ 'VAR_MODULE' => 'm', // 默认模块获取变量
+ 'VAR_ADDON' => 'addon', // 默认的插件控制器命名空间变量
+ 'VAR_CONTROLLER' => 'c', // 默认控制器获取变量
+ 'VAR_ACTION' => 'a', // 默认操作获取变量
+ 'VAR_AJAX_SUBMIT' => 'ajax', // 默认的AJAX提交变量
+ 'VAR_JSONP_HANDLER' => 'callback',
+ 'VAR_PATHINFO' => 's', // 兼容模式PATHINFO获取变量例如 ?s=/module/action/id/1 后面的参数取决于URL_PATHINFO_DEPR
+ 'VAR_TEMPLATE' => 't', // 默认模板切换变量
+ 'VAR_AUTO_STRING' => false, // 输入变量是否自动强制转换为字符串 如果开启则数组变量需要手动传入变量修饰符获取变量
+
+ 'HTTP_CACHE_CONTROL' => 'private', // 网页缓存控制
+ 'CHECK_APP_DIR' => true, // 是否检查应用目录是否创建
+ 'FILE_UPLOAD_TYPE' => 'Local', // 文件上传方式
+ 'DATA_CRYPT_TYPE' => 'Think', // 数据加密方式
+
+);
diff --git a/Framework/Mode/Lite/functions.php b/Framework/Mode/Lite/functions.php
new file mode 100644
index 00000000..d39ec173
--- /dev/null
+++ b/Framework/Mode/Lite/functions.php
@@ -0,0 +1,1543 @@
+
+// +----------------------------------------------------------------------
+
+/**
+ * Think 系统函数库
+ */
+
+/**
+ * 获取和设置配置参数 支持批量定义
+ * @param string|array $name 配置变量
+ * @param mixed $value 配置值
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+function C($name = null, $value = null, $default = null)
+{
+ static $_config = array();
+ // 无参数时获取所有
+ if (empty($name)) {
+ return $_config;
+ }
+ // 优先执行设置获取或赋值
+ if (is_string($name)) {
+ if (!strpos($name, '.')) {
+ $name = strtoupper($name);
+ if (is_null($value)) {
+ return isset($_config[$name]) ? $_config[$name] : $default;
+ }
+
+ $_config[$name] = $value;
+ return null;
+ }
+ // 二维数组设置和获取支持
+ $name = explode('.', $name);
+ $name[0] = strtoupper($name[0]);
+ if (is_null($value)) {
+ return isset($_config[$name[0]][$name[1]]) ? $_config[$name[0]][$name[1]] : $default;
+ }
+
+ $_config[$name[0]][$name[1]] = $value;
+ return null;
+ }
+ // 批量设置
+ if (is_array($name)) {
+ $_config = array_merge($_config, array_change_key_case($name, CASE_UPPER));
+ return null;
+ }
+ return null; // 避免非法参数
+}
+
+/**
+ * 加载配置文件 支持格式转换 仅支持一级配置
+ * @param string $file 配置文件名
+ * @param string $parse 配置解析方法 有些格式需要用户自己解析
+ * @return array
+ */
+function load_config($file, $parse = CONF_PARSE)
+{
+ $ext = pathinfo($file, PATHINFO_EXTENSION);
+ switch ($ext) {
+ case 'php':
+ return include $file;
+ case 'ini':
+ return parse_ini_file($file);
+ case 'yaml':
+ return yaml_parse_file($file);
+ case 'xml':
+ return (array) simplexml_load_file($file);
+ case 'json':
+ return json_decode(file_get_contents($file), true);
+ default:
+ if (function_exists($parse)) {
+ return $parse($file);
+ } else {
+ E(L('_NOT_SUPPORT_') . ':' . $ext);
+ }
+ }
+}
+
+/**
+ * 解析yaml文件返回一个数组
+ * @param string $file 配置文件名
+ * @return array
+ */
+if (!function_exists('yaml_parse_file')) {
+ function yaml_parse_file($file)
+ {
+ vendor('spyc.Spyc');
+ return Spyc::YAMLLoad($file);
+ }
+}
+
+/**
+ * 抛出异常处理
+ * @param string $msg 异常消息
+ * @param integer $code 异常代码 默认为0
+ * @throws Think\Exception
+ * @return void
+ */
+function E($msg, $code = 0)
+{
+ throw new Think\Exception($msg, $code);
+}
+
+/**
+ * 记录和统计时间(微秒)和内存使用情况
+ * 使用方法:
+ *
+ * G('begin'); // 记录开始标记位
+ * // ... 区间运行代码
+ * G('end'); // 记录结束标签位
+ * echo G('begin','end',6); // 统计区间运行时间 精确到小数后6位
+ * echo G('begin','end','m'); // 统计区间内存使用情况
+ * 如果end标记位没有定义,则会自动以当前作为标记位
+ * 其中统计内存使用需要 MEMORY_LIMIT_ON 常量为true才有效
+ *
+ * @param string $start 开始标签
+ * @param string $end 结束标签
+ * @param integer|string $dec 小数位或者m
+ * @return mixed
+ */
+function G($start, $end = '', $dec = 4)
+{
+ static $_info = array();
+ static $_mem = array();
+ if (is_float($end)) {
+ // 记录时间
+ $_info[$start] = $end;
+ } elseif (!empty($end)) {
+ // 统计时间和内存使用
+ if (!isset($_info[$end])) {
+ $_info[$end] = microtime(true);
+ }
+ if ('m' == $dec) {
+ if (!isset($_mem[$end])) {
+ $_mem[$end] = memory_get_usage();
+ }
+
+ return number_format(($_mem[$end] - $_mem[$start]) / 1024);
+ } else {
+ return number_format(($_info[$end] - $_info[$start]), $dec);
+ }
+
+ } else {
+ // 记录时间和内存使用
+ $_info[$start] = microtime(true);
+ $_mem[$start] = memory_get_usage();
+ }
+ return null;
+}
+
+/**
+ * 获取和设置语言定义(不区分大小写)
+ * @param string|array $name 语言变量
+ * @param mixed $value 语言值或者变量
+ * @return mixed
+ */
+function L($name = null, $value = null)
+{
+ static $_lang = array();
+ // 空参数返回所有定义
+ if (empty($name)) {
+ return $_lang;
+ }
+ // 判断语言获取(或设置)
+ // 若不存在,直接返回全大写$name
+ if (is_string($name)) {
+ $name = strtoupper($name);
+ if (is_null($value)) {
+ return isset($_lang[$name]) ? $_lang[$name] : $name;
+ } elseif (is_array($value)) {
+ // 支持变量
+ $replace = array_keys($value);
+ foreach ($replace as &$v) {
+ $v = '{$' . $v . '}';
+ }
+ return str_replace($replace, $value, isset($_lang[$name]) ? $_lang[$name] : $name);
+ }
+ $_lang[$name] = $value; // 语言定义
+ return null;
+ }
+ // 批量定义
+ if (is_array($name)) {
+ $_lang = array_merge($_lang, array_change_key_case($name, CASE_UPPER));
+ }
+ return null;
+}
+
+/**
+ * 添加和获取页面Trace记录
+ * @param string $value 变量
+ * @param string $label 标签
+ * @param string $level 日志级别
+ * @param boolean $record 是否记录日志
+ * @return void|array
+ */
+function trace($value = '[think]', $label = '', $level = 'DEBUG', $record = false)
+{
+ return Think\Think::trace($value, $label, $level, $record);
+}
+
+/**
+ * 编译文件
+ * @param string $filename 文件名
+ * @return string
+ */
+function compile($filename)
+{
+ $content = php_strip_whitespace($filename);
+ $content = trim(substr($content, 5));
+ // 替换预编译指令
+ $content = preg_replace('/\/\/\[RUNTIME\](.*?)\/\/\[\/RUNTIME\]/s', '', $content);
+ if (0 === strpos($content, 'namespace')) {
+ $content = preg_replace('/namespace\s(.*?);/', 'namespace \\1{', $content, 1);
+ } else {
+ $content = 'namespace {' . $content;
+ }
+ if ('?>' == substr($content, -2)) {
+ $content = substr($content, 0, -2);
+ }
+ return $content . '}';
+}
+
+/**
+ * 获取模版文件 格式 资源://模块@主题/控制器/操作
+ * @param string $template 模版资源地址
+ * @param string $layer 视图层(目录)名称
+ * @return string
+ */
+function T($template = '', $layer = '')
+{
+
+ // 解析模版资源地址
+ if (false === strpos($template, '://')) {
+ $template = 'http://' . str_replace(':', '/', $template);
+ }
+ $info = parse_url($template);
+ $file = $info['host'] . (isset($info['path']) ? $info['path'] : '');
+ $module = isset($info['user']) ? $info['user'] . '/' : MODULE_NAME . '/';
+ $extend = $info['scheme'];
+ $layer = $layer ? $layer : C('DEFAULT_V_LAYER');
+
+ // 获取当前主题的模版路径
+ $auto = C('AUTOLOAD_NAMESPACE');
+ if ($auto && isset($auto[$extend])) {
+ // 扩展资源
+ $baseUrl = $auto[$extend] . $module . $layer . '/';
+ } elseif (C('VIEW_PATH')) {
+ // 改变模块视图目录
+ $baseUrl = C('VIEW_PATH');
+ } elseif (defined('TMPL_PATH')) {
+ // 指定全局视图目录
+ $baseUrl = TMPL_PATH . $module;
+ } else {
+ $baseUrl = APP_PATH . $module . $layer . '/';
+ }
+
+ // 获取主题
+ $theme = substr_count($file, '/') < 2 ? C('DEFAULT_THEME') : '';
+
+ // 分析模板文件规则
+ $depr = C('TMPL_FILE_DEPR');
+ if ('' == $file) {
+ // 如果模板文件名为空 按照默认规则定位
+ $file = CONTROLLER_NAME . $depr . ACTION_NAME;
+ } elseif (false === strpos($file, '/')) {
+ $file = CONTROLLER_NAME . $depr . $file;
+ } elseif ('/' != $depr) {
+ $file = substr_count($file, '/') > 1 ? substr_replace($file, $depr, strrpos($file, '/'), 1) : str_replace('/', $depr, $file);
+ }
+ return $baseUrl . ($theme ? $theme . '/' : '') . $file . C('TMPL_TEMPLATE_SUFFIX');
+}
+
+/**
+ * 获取输入参数 支持过滤和默认值
+ * 使用方法:
+ *
+ * I('id',0); 获取id参数 自动判断get或者post
+ * I('post.name','','htmlspecialchars'); 获取$_POST['name']
+ * I('get.'); 获取$_GET
+ *
+ * @param string $name 变量的名称 支持指定类型
+ * @param mixed $default 不存在的时候默认值
+ * @param mixed $filter 参数过滤方法
+ * @param mixed $datas 要获取的额外数据源
+ * @return mixed
+ */
+function I($name, $default = '', $filter = null, $datas = null)
+{
+ if (strpos($name, '/')) {
+ // 指定修饰符
+ list($name, $type) = explode('/', $name, 2);
+ } elseif (C('VAR_AUTO_STRING')) {
+ // 默认强制转换为字符串
+ $type = 's';
+ }
+ if (strpos($name, '.')) {
+ // 指定参数来源
+ list($method, $name) = explode('.', $name, 2);
+ } else {
+ // 默认为自动判断
+ $method = 'param';
+ }
+ switch (strtolower($method)) {
+ case 'get':$input = &$_GET;
+ break;
+ case 'post':$input = &$_POST;
+ break;
+ case 'put':parse_str(file_get_contents('php://input'), $input);
+ break;
+ case 'param':
+ switch ($_SERVER['REQUEST_METHOD']) {
+ case 'POST':
+ $input = $_POST;
+ break;
+ case 'PUT':
+ parse_str(file_get_contents('php://input'), $input);
+ break;
+ default:
+ $input = $_GET;
+ }
+ break;
+ case 'path':
+ $input = array();
+ if (!empty($_SERVER['PATH_INFO'])) {
+ $depr = C('URL_PATHINFO_DEPR');
+ $input = explode($depr, trim($_SERVER['PATH_INFO'], $depr));
+ }
+ break;
+ case 'request':$input = &$_REQUEST;
+ break;
+ case 'session':$input = &$_SESSION;
+ break;
+ case 'cookie':$input = &$_COOKIE;
+ break;
+ case 'server':$input = &$_SERVER;
+ break;
+ case 'globals':$input = &$GLOBALS;
+ break;
+ case 'data':$input = &$datas;
+ break;
+ default:
+ return null;
+ }
+ if ('' == $name) {
+ // 获取全部变量
+ $data = $input;
+ $filters = isset($filter) ? $filter : C('DEFAULT_FILTER');
+ if ($filters) {
+ if (is_string($filters)) {
+ $filters = explode(',', $filters);
+ }
+ foreach ($filters as $filter) {
+ $data = array_map_recursive($filter, $data); // 参数过滤
+ }
+ }
+ } elseif (isset($input[$name])) {
+ // 取值操作
+ $data = $input[$name];
+ $filters = isset($filter) ? $filter : C('DEFAULT_FILTER');
+ if ($filters) {
+ if (is_string($filters)) {
+ $filters = explode(',', $filters);
+ } elseif (is_int($filters)) {
+ $filters = array($filters);
+ }
+
+ foreach ($filters as $filter) {
+ if (function_exists($filter)) {
+ $data = is_array($data) ? array_map_recursive($filter, $data) : $filter($data); // 参数过滤
+ } elseif (0 === strpos($filter, '/')) {
+ // 支持正则验证
+ if (1 !== preg_match($filter, (string) $data)) {
+ return isset($default) ? $default : null;
+ }
+ } else {
+ $data = filter_var($data, is_int($filter) ? $filter : filter_id($filter));
+ if (false === $data) {
+ return isset($default) ? $default : null;
+ }
+ }
+ }
+ }
+ if (!empty($type)) {
+ switch (strtolower($type)) {
+ case 'a': // 数组
+ $data = (array) $data;
+ break;
+ case 'd': // 数字
+ $data = (int) $data;
+ break;
+ case 'f': // 浮点
+ $data = (float) $data;
+ break;
+ case 'b': // 布尔
+ $data = (boolean) $data;
+ break;
+ case 's': // 字符串
+ default:
+ $data = (string) $data;
+ }
+ }
+ } else {
+ // 变量默认值
+ $data = isset($default) ? $default : null;
+ }
+ is_array($data) && array_walk_recursive($data, 'think_filter');
+ return $data;
+}
+
+function array_map_recursive($filter, $data)
+{
+ $result = array();
+ foreach ($data as $key => $val) {
+ $result[$key] = is_array($val)
+ ? array_map_recursive($filter, $val)
+ : call_user_func($filter, $val);
+ }
+ return $result;
+}
+
+/**
+ * 设置和获取统计数据
+ * 使用方法:
+ *
+ * N('db',1); // 记录数据库操作次数
+ * N('read',1); // 记录读取次数
+ * echo N('db'); // 获取当前页面数据库的所有操作次数
+ * echo N('read'); // 获取当前页面读取次数
+ *
+ * @param string $key 标识位置
+ * @param integer $step 步进值
+ * @param boolean $save 是否保存结果
+ * @return mixed
+ */
+function N($key, $step = 0, $save = false)
+{
+ static $_num = array();
+ if (!isset($_num[$key])) {
+ $_num[$key] = (false !== $save) ? S('N_' . $key) : 0;
+ }
+ if (empty($step)) {
+ return $_num[$key];
+ } else {
+ $_num[$key] = $_num[$key] + (int) $step;
+ }
+ if (false !== $save) {
+ // 保存结果
+ S('N_' . $key, $_num[$key], $save);
+ }
+ return null;
+}
+
+/**
+ * 字符串命名风格转换
+ * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
+ * @param string $name 字符串
+ * @param integer $type 转换类型
+ * @return string
+ */
+function parse_name($name, $type = 0)
+{
+ if ($type) {
+ return ucfirst(preg_replace_callback('/_([a-zA-Z])/', function ($match) {return strtoupper($match[1]);}, $name));
+ } else {
+ return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
+ }
+}
+
+/**
+ * 优化的require_once
+ * @param string $filename 文件地址
+ * @return boolean
+ */
+function require_cache($filename)
+{
+ static $_importFiles = array();
+ if (!isset($_importFiles[$filename])) {
+ if (file_exists_case($filename)) {
+ require $filename;
+ $_importFiles[$filename] = true;
+ } else {
+ $_importFiles[$filename] = false;
+ }
+ }
+ return $_importFiles[$filename];
+}
+
+/**
+ * 区分大小写的文件存在判断
+ * @param string $filename 文件地址
+ * @return boolean
+ */
+function file_exists_case($filename)
+{
+ if (is_file($filename)) {
+ if (IS_WIN && APP_DEBUG) {
+ if (basename(realpath($filename)) != basename($filename)) {
+ return false;
+ }
+
+ }
+ return true;
+ }
+ return false;
+}
+
+/**
+ * 导入所需的类库 同java的Import 本函数有缓存功能
+ * @param string $class 类库命名空间字符串
+ * @param string $baseUrl 起始路径
+ * @param string $ext 导入的文件扩展名
+ * @return boolean
+ */
+function import($class, $baseUrl = '', $ext = EXT)
+{
+ static $_file = array();
+ $class = str_replace(array('.', '#'), array('/', '.'), $class);
+ if (isset($_file[$class . $baseUrl])) {
+ return true;
+ } else {
+ $_file[$class . $baseUrl] = true;
+ }
+
+ $class_strut = explode('/', $class);
+ if (empty($baseUrl)) {
+ if ('@' == $class_strut[0] || MODULE_NAME == $class_strut[0]) {
+ //加载当前模块的类库
+ $baseUrl = MODULE_PATH;
+ $class = substr_replace($class, '', 0, strlen($class_strut[0]) + 1);
+ } elseif ('Common' == $class_strut[0]) {
+ //加载公共模块的类库
+ $baseUrl = COMMON_PATH;
+ $class = substr($class, 7);
+ } elseif (in_array($class_strut[0], array('Think', 'Org', 'Behavior', 'Com', 'Vendor')) || is_dir(LIB_PATH . $class_strut[0])) {
+ // 系统类库包和第三方类库包
+ $baseUrl = LIB_PATH;
+ } else {
+ // 加载其他模块的类库
+ $baseUrl = APP_PATH;
+ }
+ }
+ if (substr($baseUrl, -1) != '/') {
+ $baseUrl .= '/';
+ }
+
+ $classfile = $baseUrl . $class . $ext;
+ if (!class_exists(basename($class), false)) {
+ // 如果类不存在 则导入类库文件
+ return require_cache($classfile);
+ }
+ return null;
+}
+
+/**
+ * 基于命名空间方式导入函数库
+ * load('@.Util.Array')
+ * @param string $name 函数库命名空间字符串
+ * @param string $baseUrl 起始路径
+ * @param string $ext 导入的文件扩展名
+ * @return void
+ */
+function load($name, $baseUrl = '', $ext = '.php')
+{
+ $name = str_replace(array('.', '#'), array('/', '.'), $name);
+ if (empty($baseUrl)) {
+ if (0 === strpos($name, '@/')) {
+ //加载当前模块函数库
+ $baseUrl = MODULE_PATH . 'Common/';
+ $name = substr($name, 2);
+ } else {
+ //加载其他模块函数库
+ $array = explode('/', $name);
+ $baseUrl = APP_PATH . array_shift($array) . '/Common/';
+ $name = implode('/', $array);
+ }
+ }
+ if (substr($baseUrl, -1) != '/') {
+ $baseUrl .= '/';
+ }
+ require_cache($baseUrl . $name . $ext);
+}
+
+/**
+ * 快速导入第三方框架类库 所有第三方框架的类库文件统一放到 系统的Vendor目录下面
+ * @param string $class 类库
+ * @param string $baseUrl 基础目录
+ * @param string $ext 类库后缀
+ * @return boolean
+ */
+function vendor($class, $baseUrl = '', $ext = '.php')
+{
+ if (empty($baseUrl)) {
+ $baseUrl = VENDOR_PATH;
+ }
+ return import($class, $baseUrl, $ext);
+}
+
+/**
+ * 实例化模型类 格式 [资源://][模块/]模型
+ * @param string $name 资源地址
+ * @param string $layer 模型层名称
+ * @return Think\Model
+ */
+function D($name = '', $layer = '')
+{
+ if (empty($name)) {
+ return new Think\Model;
+ }
+
+ static $_model = array();
+ $layer = $layer ?: 'Model';
+ if (isset($_model[$name . $layer])) {
+ return $_model[$name . $layer];
+ }
+ $class = parse_res_name($name, $layer);
+ if (class_exists($class)) {
+ $model = new $class(basename($name));
+ } elseif (false === strpos($name, '/')) {
+ // 自动加载公共模块下面的模型
+ $class = '\\Common\\' . $layer . '\\' . $name . $layer;
+ $model = class_exists($class) ? new $class($name) : new Think\Model($name);
+ } else {
+ $model = new Think\Model(basename($name));
+ }
+ $_model[$name . $layer] = $model;
+ return $model;
+}
+
+/**
+ * 实例化一个没有模型文件的Model
+ * @param string $name Model名称 支持指定基础模型 例如 MongoModel:User
+ * @param string $tablePrefix 表前缀
+ * @param mixed $connection 数据库连接信息
+ * @return Think\Model
+ */
+function M($name = '', $tablePrefix = '', $connection = '')
+{
+ static $_model = array();
+ if (strpos($name, ':')) {
+ list($class, $name) = explode(':', $name);
+ } else {
+ $class = 'Think\\Model';
+ }
+ $guid = (is_array($connection) ? implode('', $connection) : $connection) . $tablePrefix . $name . '_' . $class;
+ if (!isset($_model[$guid])) {
+ $_model[$guid] = new $class($name, $tablePrefix, $connection);
+ }
+ return $_model[$guid];
+}
+
+/**
+ * 解析资源地址并导入类库文件
+ * 例如 module/controller addon://module/behavior
+ * @param string $name 资源地址 格式:[扩展://][模块/]资源名
+ * @param string $layer 分层名称
+ * @return string
+ */
+function parse_res_name($name, $layer)
+{
+ if (strpos($name, '://')) {
+// 指定扩展资源
+ list($extend, $name) = explode('://', $name);
+ } else {
+ $extend = '';
+ }
+ if (strpos($name, '/')) {
+ // 指定模块
+ list($module, $name) = explode('/', $name, 2);
+ } else {
+ $module = defined('MODULE_NAME') ? MODULE_NAME : '';
+ }
+ $array = explode('/', $name);
+ $class = $module . '\\' . $layer;
+ foreach ($array as $name) {
+ $class .= '\\' . parse_name($name, 1);
+ }
+ // 导入资源类库
+ if ($extend) {
+ // 扩展资源
+ $class = $extend . '\\' . $class;
+ }
+
+ return $class . $layer;
+}
+
+/**
+ * 用于实例化访问控制器
+ * @param string $name 控制器名
+ * @return Think\Controller|false
+ */
+function controller($name)
+{
+ $class = MODULE_NAME . '\\Controller';
+ $array = explode('/', $name);
+ foreach ($array as $name) {
+ $class .= '\\' . parse_name($name, 1);
+ }
+ $class .= $layer;
+
+ if (class_exists($class)) {
+ return new $class();
+ } else {
+ return false;
+ }
+}
+
+/**
+ * 实例化多层控制器 格式:[资源://][模块/]控制器
+ * @param string $name 资源地址
+ * @param string $layer 控制层名称
+ * @return Think\Controller|false
+ */
+function A($name, $layer = '')
+{
+ static $_action = array();
+ $layer = $layer ?: 'Controller';
+ if (isset($_action[$name . $layer])) {
+ return $_action[$name . $layer];
+ }
+
+ $class = parse_res_name($name, $layer);
+ if (class_exists($class)) {
+ $action = new $class();
+ $_action[$name . $layer] = $action;
+ return $action;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * 远程调用控制器的操作方法 URL 参数格式 [资源://][模块/]控制器/操作
+ * @param string $url 调用地址
+ * @param string|array $vars 调用参数 支持字符串和数组
+ * @param string $layer 要调用的控制层名称
+ * @return mixed
+ */
+function R($url, $vars = array(), $layer = '')
+{
+ $info = pathinfo($url);
+ $action = $info['basename'];
+ $module = $info['dirname'];
+ $class = A($module, $layer);
+ if ($class) {
+ if (is_string($vars)) {
+ parse_str($vars, $vars);
+ }
+ return call_user_func_array(array(&$class, $action . C('ACTION_SUFFIX')), $vars);
+ } else {
+ return false;
+ }
+}
+
+/**
+ * 处理标签扩展
+ * @param string $tag 标签名称
+ * @param mixed $params 传入参数
+ * @return void
+ */
+function tag($tag, &$params = null)
+{
+ \Think\Hook::listen($tag, $params);
+}
+
+/**
+ * 执行某个行为
+ * @param string $name 行为名称
+ * @param string $tag 标签名称(行为类无需传入)
+ * @param Mixed $params 传入的参数
+ * @return void
+ */
+function B($name, $tag = '', &$params = null)
+{
+ if ('' == $tag) {
+ $name .= 'Behavior';
+ }
+ return \Think\Hook::exec($name, $tag, $params);
+}
+
+/**
+ * 去除代码中的空白和注释
+ * @param string $content 代码内容
+ * @return string
+ */
+function strip_whitespace($content)
+{
+ $stripStr = '';
+ //分析php源码
+ $tokens = token_get_all($content);
+ $last_space = false;
+ for ($i = 0, $j = count($tokens); $i < $j; $i++) {
+ if (is_string($tokens[$i])) {
+ $last_space = false;
+ $stripStr .= $tokens[$i];
+ } else {
+ switch ($tokens[$i][0]) {
+ //过滤各种PHP注释
+ case T_COMMENT:
+ case T_DOC_COMMENT:
+ break;
+ //过滤空格
+ case T_WHITESPACE:
+ if (!$last_space) {
+ $stripStr .= ' ';
+ $last_space = true;
+ }
+ break;
+ case T_START_HEREDOC:
+ $stripStr .= "<<' . $label . htmlspecialchars($output, ENT_QUOTES) . '';
+ } else {
+ $output = $label . print_r($var, true);
+ }
+ } else {
+ ob_start();
+ var_dump($var);
+ $output = ob_get_clean();
+ if (!extension_loaded('xdebug')) {
+ $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output);
+ $output = '' . $label . htmlspecialchars($output, ENT_QUOTES) . ' ';
+ }
+ }
+ if ($echo) {
+ echo ($output);
+ return null;
+ } else {
+ return $output;
+ }
+
+}
+
+/**
+ * 设置当前页面的布局
+ * @param string|false $layout 布局名称 为false的时候表示关闭布局
+ * @return void
+ */
+function layout($layout)
+{
+ if (false !== $layout) {
+ // 开启布局
+ C('LAYOUT_ON', true);
+ if (is_string($layout)) {
+ // 设置新的布局模板
+ C('LAYOUT_NAME', $layout);
+ }
+ } else {
+// 临时关闭布局
+ C('LAYOUT_ON', false);
+ }
+}
+
+/**
+ * URL组装 支持不同URL模式
+ * @param string $url URL表达式,格式:'[模块/控制器/操作#锚点@域名]?参数1=值1&参数2=值2...'
+ * @param string|array $vars 传入的参数,支持数组和字符串
+ * @param string|boolean $suffix 伪静态后缀,默认为true表示获取配置值
+ * @param boolean $domain 是否显示域名
+ * @return string
+ */
+function U($url = '', $vars = '', $suffix = true, $domain = false)
+{
+ // 解析URL
+ $info = parse_url($url);
+ $url = !empty($info['path']) ? $info['path'] : ACTION_NAME;
+ if (isset($info['fragment'])) {
+ // 解析锚点
+ $anchor = $info['fragment'];
+ if (false !== strpos($anchor, '?')) {
+ // 解析参数
+ list($anchor, $info['query']) = explode('?', $anchor, 2);
+ }
+ if (false !== strpos($anchor, '@')) {
+ // 解析域名
+ list($anchor, $host) = explode('@', $anchor, 2);
+ }
+ } elseif (false !== strpos($url, '@')) {
+ // 解析域名
+ list($url, $host) = explode('@', $info['path'], 2);
+ }
+ // 解析子域名
+ if (isset($host)) {
+ $domain = $host . (strpos($host, '.') ? '' : strstr($_SERVER['HTTP_HOST'], '.'));
+ } elseif (true === $domain) {
+ $domain = $_SERVER['HTTP_HOST'];
+ if (C('APP_SUB_DOMAIN_DEPLOY')) {
+ // 开启子域名部署
+ $domain = 'localhost' == $domain ? 'localhost' : 'www' . strstr($_SERVER['HTTP_HOST'], '.');
+ // '子域名'=>array('模块[/控制器]');
+ foreach (C('APP_SUB_DOMAIN_RULES') as $key => $rule) {
+ $rule = is_array($rule) ? $rule[0] : $rule;
+ if (false === strpos($key, '*') && 0 === strpos($url, $rule)) {
+ $domain = $key . strstr($domain, '.'); // 生成对应子域名
+ $url = substr_replace($url, '', 0, strlen($rule));
+ break;
+ }
+ }
+ }
+ }
+
+ // 解析参数
+ if (is_string($vars)) {
+ // aaa=1&bbb=2 转换成数组
+ parse_str($vars, $vars);
+ } elseif (!is_array($vars)) {
+ $vars = array();
+ }
+ if (isset($info['query'])) {
+ // 解析地址里面参数 合并到vars
+ parse_str($info['query'], $params);
+ $vars = array_merge($params, $vars);
+ }
+
+ // URL组装
+ $depr = C('URL_PATHINFO_DEPR');
+ $urlCase = C('URL_CASE_INSENSITIVE');
+ if ($url) {
+ if (0 === strpos($url, '/')) {
+// 定义路由
+ $route = true;
+ $url = substr($url, 1);
+ if ('/' != $depr) {
+ $url = str_replace('/', $depr, $url);
+ }
+ } else {
+ if ('/' != $depr) {
+ // 安全替换
+ $url = str_replace('/', $depr, $url);
+ }
+ // 解析模块、控制器和操作
+ $url = trim($url, $depr);
+ $path = explode($depr, $url);
+ $var = array();
+ $varModule = C('VAR_MODULE');
+ $varController = C('VAR_CONTROLLER');
+ $varAction = C('VAR_ACTION');
+ $var[$varAction] = !empty($path) ? array_pop($path) : ACTION_NAME;
+ $var[$varController] = !empty($path) ? array_pop($path) : CONTROLLER_NAME;
+ if ($urlCase) {
+ $var[$varController] = parse_name($var[$varController]);
+ }
+ $module = '';
+
+ if (!empty($path)) {
+ $var[$varModule] = implode($depr, $path);
+ } else {
+ if (C('MULTI_MODULE')) {
+ if (MODULE_NAME != C('DEFAULT_MODULE') || !C('MODULE_ALLOW_LIST')) {
+ $var[$varModule] = MODULE_NAME;
+ }
+ }
+ }
+ if ($maps = C('URL_MODULE_MAP')) {
+ if ($_module = array_search(strtolower($var[$varModule]), $maps)) {
+ $var[$varModule] = $_module;
+ }
+ }
+ if (isset($var[$varModule])) {
+ $module = $var[$varModule];
+ unset($var[$varModule]);
+ }
+
+ }
+ }
+
+ if (C('URL_MODEL') == 0) {
+ // 普通模式URL转换
+ $url = __APP__ . '?' . C('VAR_MODULE') . "={$module}&" . http_build_query(array_reverse($var));
+ if ($urlCase) {
+ $url = strtolower($url);
+ }
+ if (!empty($vars)) {
+ $vars = http_build_query($vars);
+ $url .= '&' . $vars;
+ }
+ } else {
+ // PATHINFO模式或者兼容URL模式
+ if (isset($route)) {
+ $url = __APP__ . '/' . rtrim($url, $depr);
+ } else {
+ $module = (defined('BIND_MODULE') && BIND_MODULE == $module) ? '' : $module;
+ $url = __APP__ . '/' . ($module ? $module . MODULE_PATHINFO_DEPR : '') . implode($depr, array_reverse($var));
+ }
+ if ($urlCase) {
+ $url = strtolower($url);
+ }
+ if (!empty($vars)) {
+ // 添加参数
+ foreach ($vars as $var => $val) {
+ if ('' !== trim($val)) {
+ $url .= $depr . $var . $depr . urlencode($val);
+ }
+
+ }
+ }
+ if ($suffix) {
+ $suffix = true === $suffix ? C('URL_HTML_SUFFIX') : $suffix;
+ if ($pos = strpos($suffix, '|')) {
+ $suffix = substr($suffix, 0, $pos);
+ }
+ if ($suffix && '/' != substr($url, -1)) {
+ $url .= '.' . ltrim($suffix, '.');
+ }
+ }
+ }
+ if (isset($anchor)) {
+ $url .= '#' . $anchor;
+ }
+ if ($domain) {
+ $url = (is_ssl() ? 'https://' : 'http://') . $domain . $url;
+ }
+ return $url;
+}
+
+/**
+ * 渲染输出Widget
+ * @param string $name Widget名称
+ * @param array $data 传入的参数
+ * @return void
+ */
+function W($name, $data = array())
+{
+ return R($name, $data, 'Widget');
+}
+
+/**
+ * 判断是否SSL协议
+ * @return boolean
+ */
+function is_ssl()
+{
+ if (isset($_SERVER['HTTPS']) && ('1' == $_SERVER['HTTPS'] || 'on' == strtolower($_SERVER['HTTPS']))) {
+ return true;
+ } elseif (isset($_SERVER['SERVER_PORT']) && ('443' == $_SERVER['SERVER_PORT'])) {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * URL重定向
+ * @param string $url 重定向的URL地址
+ * @param integer $time 重定向的等待时间(秒)
+ * @param string $msg 重定向前的提示信息
+ * @return void
+ */
+function redirect($url, $time = 0, $msg = '')
+{
+ //多行URL地址支持
+ $url = str_replace(array("\n", "\r"), '', $url);
+ if (empty($msg)) {
+ $msg = "系统将在{$time}秒之后自动跳转到{$url}!";
+ }
+
+ if (!headers_sent()) {
+ // redirect
+ if (0 === $time) {
+ header('Location: ' . $url);
+ } else {
+ header("refresh:{$time};url={$url}");
+ echo ($msg);
+ }
+ exit();
+ } else {
+ $str = " ";
+ if (0 != $time) {
+ $str .= $msg;
+ }
+
+ exit($str);
+ }
+}
+
+/**
+ * 缓存管理
+ * @param mixed $name 缓存名称,如果为数组表示进行缓存设置
+ * @param mixed $value 缓存值
+ * @param mixed $options 缓存参数
+ * @return mixed
+ */
+function S($name, $value = '', $options = null)
+{
+ static $cache = '';
+ if (is_array($options) && empty($cache)) {
+ // 缓存操作的同时初始化
+ $type = isset($options['type']) ? $options['type'] : '';
+ $cache = Think\Cache::getInstance($type, $options);
+ } elseif (is_array($name)) {
+ // 缓存初始化
+ $type = isset($name['type']) ? $name['type'] : '';
+ $cache = Think\Cache::getInstance($type, $name);
+ return $cache;
+ } elseif (empty($cache)) {
+ // 自动初始化
+ $cache = Think\Cache::getInstance();
+ }
+ if ('' === $value) {
+ // 获取缓存
+ return $cache->get($name);
+ } elseif (is_null($value)) {
+ // 删除缓存
+ return $cache->rm($name);
+ } else {
+ // 缓存数据
+ if (is_array($options)) {
+ $expire = isset($options['expire']) ? $options['expire'] : null;
+ } else {
+ $expire = is_numeric($options) ? $options : null;
+ }
+ return $cache->set($name, $value, $expire);
+ }
+}
+
+/**
+ * 快速文件数据读取和保存 针对简单类型数据 字符串、数组
+ * @param string $name 缓存名称
+ * @param mixed $value 缓存值
+ * @param string $path 缓存路径
+ * @return mixed
+ */
+function F($name, $value = '', $path = DATA_PATH)
+{
+ static $_cache = array();
+ $filename = $path . $name . '.php';
+ if ('' !== $value) {
+ if (is_null($value)) {
+ // 删除缓存
+ if (false !== strpos($name, '*')) {
+ return false; // TODO
+ } else {
+ unset($_cache[$name]);
+ return Think\Storage::unlink($filename, 'F');
+ }
+ } else {
+ Think\Storage::put($filename, serialize($value), 'F');
+ // 缓存数据
+ $_cache[$name] = $value;
+ return null;
+ }
+ }
+ // 获取缓存数据
+ if (isset($_cache[$name])) {
+ return $_cache[$name];
+ }
+
+ if (Think\Storage::has($filename, 'F')) {
+ $value = unserialize(Think\Storage::read($filename, 'F'));
+ $_cache[$name] = $value;
+ } else {
+ $value = false;
+ }
+ return $value;
+}
+
+/**
+ * 根据PHP各种类型变量生成唯一标识号
+ * @param mixed $mix 变量
+ * @return string
+ */
+function to_guid_string($mix)
+{
+ if (is_object($mix)) {
+ return spl_object_hash($mix);
+ } elseif (is_resource($mix)) {
+ $mix = get_resource_type($mix) . strval($mix);
+ } else {
+ $mix = serialize($mix);
+ }
+ return md5($mix);
+}
+
+/**
+ * session管理函数
+ * @param string|array $name session名称 如果为数组则表示进行session设置
+ * @param mixed $value session值
+ * @return mixed
+ */
+function session($name = '', $value = '')
+{
+ $prefix = C('SESSION_PREFIX');
+ if (is_array($name)) {
+ // session初始化 在session_start 之前调用
+ if (isset($name['prefix'])) {
+ C('SESSION_PREFIX', $name['prefix']);
+ }
+
+ if (C('VAR_SESSION_ID') && isset($_REQUEST[C('VAR_SESSION_ID')])) {
+ session_id($_REQUEST[C('VAR_SESSION_ID')]);
+ } elseif (isset($name['id'])) {
+ session_id($name['id']);
+ }
+ if ('common' != APP_MODE) {
+ // 其它模式可能不支持
+ ini_set('session.auto_start', 0);
+ }
+ if (isset($name['name'])) {
+ session_name($name['name']);
+ }
+
+ if (isset($name['path'])) {
+ session_save_path($name['path']);
+ }
+
+ if (isset($name['domain'])) {
+ ini_set('session.cookie_domain', $name['domain']);
+ }
+
+ if (isset($name['expire'])) {
+ ini_set('session.gc_maxlifetime', $name['expire']);
+ }
+
+ if (isset($name['use_trans_sid'])) {
+ ini_set('session.use_trans_sid', $name['use_trans_sid'] ? 1 : 0);
+ }
+
+ if (isset($name['use_cookies'])) {
+ ini_set('session.use_cookies', $name['use_cookies'] ? 1 : 0);
+ }
+
+ if (isset($name['cache_limiter'])) {
+ session_cache_limiter($name['cache_limiter']);
+ }
+
+ if (isset($name['cache_expire'])) {
+ session_cache_expire($name['cache_expire']);
+ }
+
+ if (isset($name['type'])) {
+ C('SESSION_TYPE', $name['type']);
+ }
+
+ if (C('SESSION_TYPE')) {
+ // 读取session驱动
+ $type = C('SESSION_TYPE');
+ $class = strpos($type, '\\') ? $type : 'Think\\Session\\Driver\\' . ucwords(strtolower($type));
+ $hander = new $class();
+ session_set_save_handler(
+ array(&$hander, "open"),
+ array(&$hander, "close"),
+ array(&$hander, "read"),
+ array(&$hander, "write"),
+ array(&$hander, "destroy"),
+ array(&$hander, "gc"));
+ }
+ // 启动session
+ if (C('SESSION_AUTO_START')) {
+ session_start();
+ }
+
+ } elseif ('' === $value) {
+ if ('' === $name) {
+ // 获取全部的session
+ return $prefix ? $_SESSION[$prefix] : $_SESSION;
+ } elseif (0 === strpos($name, '[')) {
+ // session 操作
+ if ('[pause]' == $name) {
+ // 暂停session
+ session_write_close();
+ } elseif ('[start]' == $name) {
+ // 启动session
+ session_start();
+ } elseif ('[destroy]' == $name) {
+ // 销毁session
+ $_SESSION = array();
+ session_unset();
+ session_destroy();
+ } elseif ('[regenerate]' == $name) {
+ // 重新生成id
+ session_regenerate_id();
+ }
+ } elseif (0 === strpos($name, '?')) {
+ // 检查session
+ $name = substr($name, 1);
+ if (strpos($name, '.')) {
+ // 支持数组
+ list($name1, $name2) = explode('.', $name);
+ return $prefix ? isset($_SESSION[$prefix][$name1][$name2]) : isset($_SESSION[$name1][$name2]);
+ } else {
+ return $prefix ? isset($_SESSION[$prefix][$name]) : isset($_SESSION[$name]);
+ }
+ } elseif (is_null($name)) {
+ // 清空session
+ if ($prefix) {
+ unset($_SESSION[$prefix]);
+ } else {
+ $_SESSION = array();
+ }
+ } elseif ($prefix) {
+ // 获取session
+ if (strpos($name, '.')) {
+ list($name1, $name2) = explode('.', $name);
+ return isset($_SESSION[$prefix][$name1][$name2]) ? $_SESSION[$prefix][$name1][$name2] : null;
+ } else {
+ return isset($_SESSION[$prefix][$name]) ? $_SESSION[$prefix][$name] : null;
+ }
+ } else {
+ if (strpos($name, '.')) {
+ list($name1, $name2) = explode('.', $name);
+ return isset($_SESSION[$name1][$name2]) ? $_SESSION[$name1][$name2] : null;
+ } else {
+ return isset($_SESSION[$name]) ? $_SESSION[$name] : null;
+ }
+ }
+ } elseif (is_null($value)) {
+ // 删除session
+ if (strpos($name, '.')) {
+ list($name1, $name2) = explode('.', $name);
+ if ($prefix) {
+ unset($_SESSION[$prefix][$name1][$name2]);
+ } else {
+ unset($_SESSION[$name1][$name2]);
+ }
+ } else {
+ if ($prefix) {
+ unset($_SESSION[$prefix][$name]);
+ } else {
+ unset($_SESSION[$name]);
+ }
+ }
+ } else {
+ // 设置session
+ if (strpos($name, '.')) {
+ list($name1, $name2) = explode('.', $name);
+ if ($prefix) {
+ $_SESSION[$prefix][$name1][$name2] = $value;
+ } else {
+ $_SESSION[$name1][$name2] = $value;
+ }
+ } else {
+ if ($prefix) {
+ $_SESSION[$prefix][$name] = $value;
+ } else {
+ $_SESSION[$name] = $value;
+ }
+ }
+ }
+ return null;
+}
+
+/**
+ * Cookie 设置、获取、删除
+ * @param string $name cookie名称
+ * @param mixed $value cookie值
+ * @param mixed $option cookie参数
+ * @return mixed
+ */
+function cookie($name = '', $value = '', $option = null)
+{
+ // 默认设置
+ $config = array(
+ 'prefix' => C('COOKIE_PREFIX'), // cookie 名称前缀
+ 'expire' => C('COOKIE_EXPIRE'), // cookie 保存时间
+ 'path' => C('COOKIE_PATH'), // cookie 保存路径
+ 'domain' => C('COOKIE_DOMAIN'), // cookie 有效域名
+ 'secure' => C('COOKIE_SECURE'), // cookie 启用安全传输
+ 'httponly' => C('COOKIE_HTTPONLY'), // httponly设置
+ );
+ // 参数设置(会覆盖黙认设置)
+ if (!is_null($option)) {
+ if (is_numeric($option)) {
+ $option = array('expire' => $option);
+ } elseif (is_string($option)) {
+ parse_str($option, $option);
+ }
+
+ $config = array_merge($config, array_change_key_case($option));
+ }
+ if (!empty($config['httponly'])) {
+ ini_set("session.cookie_httponly", 1);
+ }
+ // 清除指定前缀的所有cookie
+ if (is_null($name)) {
+ if (empty($_COOKIE)) {
+ return null;
+ }
+
+ // 要删除的cookie前缀,不指定则删除config设置的指定前缀
+ $prefix = empty($value) ? $config['prefix'] : $value;
+ if (!empty($prefix)) {
+// 如果前缀为空字符串将不作处理直接返回
+ foreach ($_COOKIE as $key => $val) {
+ if (0 === stripos($key, $prefix)) {
+ setcookie($key, '', time() - 3600, $config['path'], $config['domain'], $config['secure'], $config['httponly']);
+ unset($_COOKIE[$key]);
+ }
+ }
+ }
+ return null;
+ } elseif ('' === $name) {
+ // 获取全部的cookie
+ return $_COOKIE;
+ }
+ $name = $config['prefix'] . str_replace('.', '_', $name);
+ if ('' === $value) {
+ if (isset($_COOKIE[$name])) {
+ $value = $_COOKIE[$name];
+ if (0 === strpos($value, 'think:')) {
+ $value = substr($value, 6);
+ return array_map('urldecode', json_decode(MAGIC_QUOTES_GPC ? stripslashes($value) : $value, true));
+ } else {
+ return $value;
+ }
+ } else {
+ return null;
+ }
+ } else {
+ if (is_null($value)) {
+ setcookie($name, '', time() - 3600, $config['path'], $config['domain'], $config['secure'], $config['httponly']);
+ unset($_COOKIE[$name]); // 删除指定cookie
+ } else {
+ // 设置cookie
+ if (is_array($value)) {
+ $value = 'think:' . json_encode(array_map('urlencode', $value));
+ }
+ $expire = !empty($config['expire']) ? time() + intval($config['expire']) : 0;
+ setcookie($name, $value, $expire, $config['path'], $config['domain'], $config['secure'], $config['httponly']);
+ $_COOKIE[$name] = $value;
+ }
+ }
+ return null;
+}
+
+/**
+ * 发送HTTP状态
+ * @param integer $code 状态码
+ * @return void
+ */
+function send_http_status($code)
+{
+ static $_status = array(
+ // Informational 1xx
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ // Success 2xx
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ // Redirection 3xx
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Moved Temporarily ', // 1.1
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ // 306 is deprecated but reserved
+ 307 => 'Temporary Redirect',
+ // Client Error 4xx
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ // Server Error 5xx
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ 509 => 'Bandwidth Limit Exceeded',
+ );
+ if (isset($_status[$code])) {
+ header('HTTP/1.1 ' . $code . ' ' . $_status[$code]);
+ // 确保FastCGI模式下正常
+ header('Status:' . $code . ' ' . $_status[$code]);
+ }
+}
+
+function think_filter(&$value)
+{
+ // TODO 其他安全过滤
+
+ // 过滤查询特殊字符
+ if (preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i', $value)) {
+ $value .= ' ';
+ }
+}
+
+// 不区分大小写的in_array实现
+function in_array_case($value, $array)
+{
+ return in_array(strtolower($value), array_map('strtolower', $array));
+}
diff --git a/Framework/Mode/Sae/convention.php b/Framework/Mode/Sae/convention.php
new file mode 100644
index 00000000..d38180da
--- /dev/null
+++ b/Framework/Mode/Sae/convention.php
@@ -0,0 +1,38 @@
+
+// +----------------------------------------------------------------------
+
+/**
+ * SAE模式惯例配置文件
+ * 该文件请不要修改,如果要覆盖惯例配置的值,可在应用配置文件中设定和惯例不符的配置项
+ * 配置名称大小写任意,系统会统一转换成小写
+ * 所有配置参数都可以在生效前动态改变
+ */
+$st = new SaeStorage();
+return array(
+ //SAE下固定mysql配置
+ 'DB_TYPE' => 'mysql', // 数据库类型
+ 'DB_DEPLOY_TYPE' => 1,
+ 'DB_RW_SEPARATE' => true,
+ 'DB_HOST' => SAE_MYSQL_HOST_M . ',' . SAE_MYSQL_HOST_S, // 服务器地址
+ 'DB_NAME' => SAE_MYSQL_DB, // 数据库名
+ 'DB_USER' => SAE_MYSQL_USER, // 用户名
+ 'DB_PWD' => SAE_MYSQL_PASS, // 密码
+ 'DB_PORT' => SAE_MYSQL_PORT, // 端口
+ //更改模板替换变量,让普通能在所有平台下显示
+ 'TMPL_PARSE_STRING' => array(
+ // __PUBLIC__/upload --> /Public/upload -->http://appname-public.stor.sinaapp.com/upload
+ '/Public/upload' => $st->getUrl('public', 'upload'),
+ ),
+ 'LOG_TYPE' => 'Sae',
+ 'DATA_CACHE_TYPE' => 'Memcachesae',
+ 'CHECK_APP_DIR' => false,
+ 'FILE_UPLOAD_TYPE' => 'Sae',
+);
diff --git a/Framework/Mode/api.php b/Framework/Mode/api.php
new file mode 100644
index 00000000..8500bb99
--- /dev/null
+++ b/Framework/Mode/api.php
@@ -0,0 +1,44 @@
+
+// +----------------------------------------------------------------------
+
+/**
+ * ThinkPHP API模式定义
+ */
+return array(
+ // 配置文件
+ 'config' => array(
+ THINK_PATH . 'Conf/convention.php', // 系统惯例配置
+ CONF_PATH . 'config' . CONF_EXT, // 应用公共配置
+ ),
+
+ // 别名定义
+ 'alias' => array(
+ 'Think\Exception' => CORE_PATH . 'Exception' . EXT,
+ 'Think\Model' => CORE_PATH . 'Model' . EXT,
+ 'Think\Db' => CORE_PATH . 'Db' . EXT,
+ 'Think\Cache' => CORE_PATH . 'Cache' . EXT,
+ 'Think\Cache\Driver\File' => CORE_PATH . 'Cache/Driver/File' . EXT,
+ 'Think\Storage' => CORE_PATH . 'Storage' . EXT,
+ ),
+
+ // 函数和类文件
+ 'core' => array(
+ MODE_PATH . 'Api/functions.php',
+ COMMON_PATH . 'Common/function.php',
+ MODE_PATH . 'Api/App' . EXT,
+ MODE_PATH . 'Api/Dispatcher' . EXT,
+ MODE_PATH . 'Api/Controller' . EXT,
+ CORE_PATH . 'Behavior' . EXT,
+ ),
+ // 行为扩展定义
+ 'tags' => array(
+ ),
+);
diff --git a/Framework/Mode/common.php b/Framework/Mode/common.php
new file mode 100644
index 00000000..26d5137e
--- /dev/null
+++ b/Framework/Mode/common.php
@@ -0,0 +1,71 @@
+
+// +----------------------------------------------------------------------
+
+/**
+ * ThinkPHP 普通模式定义
+ */
+return array(
+ // 配置文件
+ 'config' => array(
+ THINK_PATH . 'Conf/convention.php', // 系统惯例配置
+ CONF_PATH . 'config' . CONF_EXT, // 应用公共配置
+ ),
+
+ // 别名定义
+ 'alias' => array(
+ 'Think\Log' => CORE_PATH . 'Log' . EXT,
+ 'Think\Log\Driver\File' => CORE_PATH . 'Log/Driver/File' . EXT,
+ 'Think\Exception' => CORE_PATH . 'Exception' . EXT,
+ 'Think\Model' => CORE_PATH . 'Model' . EXT,
+ 'Think\Db' => CORE_PATH . 'Db' . EXT,
+ 'Think\Template' => CORE_PATH . 'Template' . EXT,
+ 'Think\Cache' => CORE_PATH . 'Cache' . EXT,
+ 'Think\Cache\Driver\File' => CORE_PATH . 'Cache/Driver/File' . EXT,
+ 'Think\Storage' => CORE_PATH . 'Storage' . EXT,
+ ),
+
+ // 函数和类文件
+ 'core' => array(
+ THINK_PATH . 'Common/functions.php',
+ COMMON_PATH . 'Common/function.php',
+ CORE_PATH . 'Hook' . EXT,
+ CORE_PATH . 'App' . EXT,
+ CORE_PATH . 'Dispatcher' . EXT,
+ //CORE_PATH . 'Log'.EXT,
+ CORE_PATH . 'Route' . EXT,
+ CORE_PATH . 'Controller' . EXT,
+ CORE_PATH . 'View' . EXT,
+ BEHAVIOR_PATH . 'BuildLiteBehavior' . EXT,
+ BEHAVIOR_PATH . 'ParseTemplateBehavior' . EXT,
+ BEHAVIOR_PATH . 'ContentReplaceBehavior' . EXT,
+ ),
+ // 行为扩展定义
+ 'tags' => array(
+ 'app_init' => array(
+ 'Behavior\BuildLiteBehavior', // 生成运行Lite文件
+ ),
+ 'app_begin' => array(
+ 'Behavior\ReadHtmlCacheBehavior', // 读取静态缓存
+ ),
+ 'app_end' => array(
+ 'Behavior\ShowPageTraceBehavior', // 页面Trace显示
+ ),
+ 'view_parse' => array(
+ 'Behavior\ParseTemplateBehavior', // 模板解析 支持PHP、内置模板引擎和第三方模板引擎
+ ),
+ 'template_filter' => array(
+ 'Behavior\ContentReplaceBehavior', // 模板输出替换
+ ),
+ 'view_filter' => array(
+ 'Behavior\WriteHtmlCacheBehavior', // 写入静态缓存
+ ),
+ ),
+);
diff --git a/Framework/Mode/lite.php b/Framework/Mode/lite.php
new file mode 100644
index 00000000..7f4c7aea
--- /dev/null
+++ b/Framework/Mode/lite.php
@@ -0,0 +1,47 @@
+
+// +----------------------------------------------------------------------
+
+/**
+ * ThinkPHP Lite模式定义
+ */
+return array(
+ // 配置文件
+ 'config' => array(
+ MODE_PATH . 'Lite/convention.php', // 系统惯例配置
+ CONF_PATH . 'config' . CONF_EXT, // 应用公共配置
+ ),
+
+ // 别名定义
+ 'alias' => array(
+ 'Think\Exception' => CORE_PATH . 'Exception' . EXT,
+ 'Think\Model' => CORE_PATH . 'Model' . EXT,
+ 'Think\Db' => CORE_PATH . 'Db' . EXT,
+ 'Think\Cache' => CORE_PATH . 'Cache' . EXT,
+ 'Think\Cache\Driver\File' => CORE_PATH . 'Cache/Driver/File' . EXT,
+ 'Think\Storage' => CORE_PATH . 'Storage' . EXT,
+ ),
+
+ // 函数和类文件
+ 'core' => array(
+ MODE_PATH . 'Lite/functions.php',
+ COMMON_PATH . 'Common/function.php',
+ CORE_PATH . 'Hook' . EXT,
+ CORE_PATH . 'App' . EXT,
+ CORE_PATH . 'Dispatcher' . EXT,
+ //CORE_PATH . 'Log'.EXT,
+ CORE_PATH . 'Route' . EXT,
+ CORE_PATH . 'Controller' . EXT,
+ CORE_PATH . 'View' . EXT,
+ ),
+ // 行为扩展定义
+ 'tags' => array(
+ ),
+);
diff --git a/Framework/Mode/sae.php b/Framework/Mode/sae.php
new file mode 100644
index 00000000..6822103d
--- /dev/null
+++ b/Framework/Mode/sae.php
@@ -0,0 +1,68 @@
+
+// +----------------------------------------------------------------------
+
+/**
+ * ThinkPHP SAE应用模式定义文件
+ */
+return array(
+ // 配置文件
+ 'config' => array(
+ THINK_PATH . 'Conf/convention.php', // 系统惯例配置
+ CONF_PATH . 'config' . CONF_EXT, // 应用公共配置
+ MODE_PATH . 'Sae/convention.php', //[sae] sae的惯例配置
+ ),
+
+ // 别名定义
+ 'alias' => array(
+ 'Think\Log' => CORE_PATH . 'Log' . EXT,
+ 'Think\Log\Driver\File' => CORE_PATH . 'Log/Driver/File' . EXT,
+ 'Think\Exception' => CORE_PATH . 'Exception' . EXT,
+ 'Think\Model' => CORE_PATH . 'Model' . EXT,
+ 'Think\Db' => CORE_PATH . 'Db' . EXT,
+ 'Think\Template' => CORE_PATH . 'Template' . EXT,
+ 'Think\Cache' => CORE_PATH . 'Cache' . EXT,
+ 'Think\Cache\Driver\File' => CORE_PATH . 'Cache/Driver/File' . EXT,
+ 'Think\Storage' => CORE_PATH . 'Storage' . EXT,
+ ),
+
+ // 函数和类文件
+ 'core' => array(
+ THINK_PATH . 'Common/functions.php',
+ COMMON_PATH . 'Common/function.php',
+ CORE_PATH . 'Hook' . EXT,
+ CORE_PATH . 'App' . EXT,
+ CORE_PATH . 'Dispatcher' . EXT,
+ //CORE_PATH . 'Log'.EXT,
+ CORE_PATH . 'Route' . EXT,
+ CORE_PATH . 'Controller' . EXT,
+ CORE_PATH . 'View' . EXT,
+ BEHAVIOR_PATH . 'ParseTemplateBehavior' . EXT,
+ BEHAVIOR_PATH . 'ContentReplaceBehavior' . EXT,
+ ),
+ // 行为扩展定义
+ 'tags' => array(
+ 'app_begin' => array(
+ 'Behavior\ReadHtmlCacheBehavior', // 读取静态缓存
+ ),
+ 'app_end' => array(
+ 'Behavior\ShowPageTraceBehavior', // 页面Trace显示
+ ),
+ 'view_parse' => array(
+ 'Behavior\ParseTemplateBehavior', // 模板解析 支持PHP、内置模板引擎和第三方模板引擎
+ ),
+ 'template_filter' => array(
+ 'Behavior\ContentReplaceBehavior', // 模板输出替换
+ ),
+ 'view_filter' => array(
+ 'Behavior\WriteHtmlCacheBehavior', // 写入静态缓存
+ ),
+ ),
+);
diff --git a/Framework/Tpl/dispatch_jump.tpl b/Framework/Tpl/dispatch_jump.tpl
new file mode 100644
index 00000000..25d4e4ef
--- /dev/null
+++ b/Framework/Tpl/dispatch_jump.tpl
@@ -0,0 +1,49 @@
+
+
+
+
+
+跳转提示
+
+
+
+
+
+
:)
+
+
+
:(
+
+
+
+
+页面自动 跳转 等待时间:
+
+
+
+
+
diff --git a/Framework/Tpl/page_trace.tpl b/Framework/Tpl/page_trace.tpl
new file mode 100644
index 00000000..8bd37030
--- /dev/null
+++ b/Framework/Tpl/page_trace.tpl
@@ -0,0 +1,67 @@
+
+
+
+ $value){ ?>
+
+
+
+
+
+
+
+ $val){
+ echo '' . (is_numeric($k) ? '' : $k.' : ') . htmlentities($val,ENT_COMPAT,'utf-8') .' ';
+ }
+ }
+ ?>
+
+
+
+
+
+
+
+
+
diff --git a/Framework/Tpl/think_exception.tpl b/Framework/Tpl/think_exception.tpl
new file mode 100644
index 00000000..e4afe4fa
--- /dev/null
+++ b/Framework/Tpl/think_exception.tpl
@@ -0,0 +1,53 @@
+
+
+
+系统发生错误
+
+
+
+
+
+
ThinkPHP { Fast & Simple OOP PHP Framework } -- [ WE CAN DO IT JUST THINK ]
+
+
+
diff --git a/Framework/logo.png b/Framework/logo.png
new file mode 100644
index 00000000..e0b195d8
Binary files /dev/null and b/Framework/logo.png differ
diff --git a/Public/libs/jquery/1.x/jquery.js b/Public/libs/jquery/1.x/jquery.js
new file mode 100644
index 00000000..6feb1108
--- /dev/null
+++ b/Public/libs/jquery/1.x/jquery.js
@@ -0,0 +1,10351 @@
+/*!
+ * jQuery JavaScript Library v1.11.3
+ * http://jquery.com/
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ *
+ * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2015-04-28T16:19Z
+ */
+
+(function( global, factory ) {
+
+ if ( typeof module === "object" && typeof module.exports === "object" ) {
+ // For CommonJS and CommonJS-like environments where a proper window is present,
+ // execute the factory and get jQuery
+ // For environments that do not inherently posses a window with a document
+ // (such as Node.js), expose a jQuery-making factory as module.exports
+ // This accentuates the need for the creation of a real window
+ // e.g. var jQuery = require("jquery")(window);
+ // See ticket #14549 for more info
+ module.exports = global.document ?
+ factory( global, true ) :
+ function( w ) {
+ if ( !w.document ) {
+ throw new Error( "jQuery requires a window with a document" );
+ }
+ return factory( w );
+ };
+ } else {
+ factory( global );
+ }
+
+// Pass this if window is not defined yet
+}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+
+// Can't do this because several apps including ASP.NET trace
+// the stack via arguments.caller.callee and Firefox dies if
+// you try to trace through "use strict" call chains. (#13335)
+// Support: Firefox 18+
+//
+
+var deletedIds = [];
+
+var slice = deletedIds.slice;
+
+var concat = deletedIds.concat;
+
+var push = deletedIds.push;
+
+var indexOf = deletedIds.indexOf;
+
+var class2type = {};
+
+var toString = class2type.toString;
+
+var hasOwn = class2type.hasOwnProperty;
+
+var support = {};
+
+
+
+var
+ version = "1.11.3",
+
+ // Define a local copy of jQuery
+ jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ // Need init if jQuery is called (just allow error to be thrown if not included)
+ return new jQuery.fn.init( selector, context );
+ },
+
+ // Support: Android<4.1, IE<9
+ // Make sure we trim BOM and NBSP
+ rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+
+ // Matches dashed string for camelizing
+ rmsPrefix = /^-ms-/,
+ rdashAlpha = /-([\da-z])/gi,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return letter.toUpperCase();
+ };
+
+jQuery.fn = jQuery.prototype = {
+ // The current version of jQuery being used
+ jquery: version,
+
+ constructor: jQuery,
+
+ // Start with an empty selector
+ selector: "",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ toArray: function() {
+ return slice.call( this );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num != null ?
+
+ // Return just the one element from the set
+ ( num < 0 ? this[ num + this.length ] : this[ num ] ) :
+
+ // Return all the elements in a clean array
+ slice.call( this );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems ) {
+
+ // Build a new jQuery matched element set
+ var ret = jQuery.merge( this.constructor(), elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+ ret.context = this.context;
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ slice: function() {
+ return this.pushStack( slice.apply( this, arguments ) );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ eq: function( i ) {
+ var len = this.length,
+ j = +i + ( i < 0 ? len : 0 );
+ return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: push,
+ sort: deletedIds.sort,
+ splice: deletedIds.splice
+};
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var src, copyIsArray, copy, name, options, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+
+ // skip the boolean and the target
+ target = arguments[ i ] || {};
+ i++;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( i === length ) {
+ target = this;
+ i--;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ // Unique for each copy of jQuery on the page
+ expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
+
+ // Assume jQuery is ready without the ready module
+ isReady: true,
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ noop: function() {},
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray || function( obj ) {
+ return jQuery.type(obj) === "array";
+ },
+
+ isWindow: function( obj ) {
+ /* jshint eqeqeq: false */
+ return obj != null && obj == obj.window;
+ },
+
+ isNumeric: function( obj ) {
+ // parseFloat NaNs numeric-cast false positives (null|true|false|"")
+ // ...but misinterprets leading-number strings, particularly hex literals ("0x...")
+ // subtraction forces infinities to NaN
+ // adding 1 corrects loss of precision from parseFloat (#15100)
+ return !jQuery.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0;
+ },
+
+ isEmptyObject: function( obj ) {
+ var name;
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ isPlainObject: function( obj ) {
+ var key;
+
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ try {
+ // Not own constructor property must be Object
+ if ( obj.constructor &&
+ !hasOwn.call(obj, "constructor") &&
+ !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ return false;
+ }
+ } catch ( e ) {
+ // IE8,9 Will throw exceptions on certain host objects #9897
+ return false;
+ }
+
+ // Support: IE<9
+ // Handle iteration over inherited properties before own properties.
+ if ( support.ownLast ) {
+ for ( key in obj ) {
+ return hasOwn.call( obj, key );
+ }
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+ for ( key in obj ) {}
+
+ return key === undefined || hasOwn.call( obj, key );
+ },
+
+ type: function( obj ) {
+ if ( obj == null ) {
+ return obj + "";
+ }
+ return typeof obj === "object" || typeof obj === "function" ?
+ class2type[ toString.call(obj) ] || "object" :
+ typeof obj;
+ },
+
+ // Evaluates a script in a global context
+ // Workarounds based on findings by Jim Driscoll
+ // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+ globalEval: function( data ) {
+ if ( data && jQuery.trim( data ) ) {
+ // We use execScript on Internet Explorer
+ // We use an anonymous function so that context is window
+ // rather than jQuery in Firefox
+ ( window.execScript || function( data ) {
+ window[ "eval" ].call( window, data );
+ } )( data );
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+ },
+
+ // args is for internal usage only
+ each: function( obj, callback, args ) {
+ var value,
+ i = 0,
+ length = obj.length,
+ isArray = isArraylike( obj );
+
+ if ( args ) {
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback.apply( obj[ i ], args );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ value = callback.apply( obj[ i ], args );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback.call( obj[ i ], i, obj[ i ] );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ value = callback.call( obj[ i ], i, obj[ i ] );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return obj;
+ },
+
+ // Support: Android<4.1, IE<9
+ trim: function( text ) {
+ return text == null ?
+ "" :
+ ( text + "" ).replace( rtrim, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( arr, results ) {
+ var ret = results || [];
+
+ if ( arr != null ) {
+ if ( isArraylike( Object(arr) ) ) {
+ jQuery.merge( ret,
+ typeof arr === "string" ?
+ [ arr ] : arr
+ );
+ } else {
+ push.call( ret, arr );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, arr, i ) {
+ var len;
+
+ if ( arr ) {
+ if ( indexOf ) {
+ return indexOf.call( arr, elem, i );
+ }
+
+ len = arr.length;
+ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+ for ( ; i < len; i++ ) {
+ // Skip accessing in sparse arrays
+ if ( i in arr && arr[ i ] === elem ) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ var len = +second.length,
+ j = 0,
+ i = first.length;
+
+ while ( j < len ) {
+ first[ i++ ] = second[ j++ ];
+ }
+
+ // Support: IE<9
+ // Workaround casting of .length to NaN on otherwise arraylike objects (e.g., NodeLists)
+ if ( len !== len ) {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, invert ) {
+ var callbackInverse,
+ matches = [],
+ i = 0,
+ length = elems.length,
+ callbackExpect = !invert;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( ; i < length; i++ ) {
+ callbackInverse = !callback( elems[ i ], i );
+ if ( callbackInverse !== callbackExpect ) {
+ matches.push( elems[ i ] );
+ }
+ }
+
+ return matches;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value,
+ i = 0,
+ length = elems.length,
+ isArray = isArraylike( elems ),
+ ret = [];
+
+ // Go through the array, translating each of the items to their new values
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret.push( value );
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( i in elems ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret.push( value );
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ var args, proxy, tmp;
+
+ if ( typeof context === "string" ) {
+ tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ args = slice.call( arguments, 2 );
+ proxy = function() {
+ return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ now: function() {
+ return +( new Date() );
+ },
+
+ // jQuery.support is not used in Core but other projects attach their
+ // properties to it so it needs to exist.
+ support: support
+});
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+function isArraylike( obj ) {
+
+ // Support: iOS 8.2 (not reproducible in simulator)
+ // `in` check used to prevent JIT error (gh-2145)
+ // hasOwn isn't used here due to false negatives
+ // regarding Nodelist length in IE
+ var length = "length" in obj && obj.length,
+ type = jQuery.type( obj );
+
+ if ( type === "function" || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ if ( obj.nodeType === 1 && length ) {
+ return true;
+ }
+
+ return type === "array" || length === 0 ||
+ typeof length === "number" && length > 0 && ( length - 1 ) in obj;
+}
+var Sizzle =
+/*!
+ * Sizzle CSS Selector Engine v2.2.0-pre
+ * http://sizzlejs.com/
+ *
+ * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2014-12-16
+ */
+(function( window ) {
+
+var i,
+ support,
+ Expr,
+ getText,
+ isXML,
+ tokenize,
+ compile,
+ select,
+ outermostContext,
+ sortInput,
+ hasDuplicate,
+
+ // Local document vars
+ setDocument,
+ document,
+ docElem,
+ documentIsHTML,
+ rbuggyQSA,
+ rbuggyMatches,
+ matches,
+ contains,
+
+ // Instance-specific data
+ expando = "sizzle" + 1 * new Date(),
+ preferredDoc = window.document,
+ dirruns = 0,
+ done = 0,
+ classCache = createCache(),
+ tokenCache = createCache(),
+ compilerCache = createCache(),
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ },
+
+ // General-purpose constants
+ MAX_NEGATIVE = 1 << 31,
+
+ // Instance methods
+ hasOwn = ({}).hasOwnProperty,
+ arr = [],
+ pop = arr.pop,
+ push_native = arr.push,
+ push = arr.push,
+ slice = arr.slice,
+ // Use a stripped-down indexOf as it's faster than native
+ // http://jsperf.com/thor-indexof-vs-for/5
+ indexOf = function( list, elem ) {
+ var i = 0,
+ len = list.length;
+ for ( ; i < len; i++ ) {
+ if ( list[i] === elem ) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
+
+ // Regular expressions
+
+ // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
+ whitespace = "[\\x20\\t\\r\\n\\f]",
+ // http://www.w3.org/TR/css3-syntax/#characters
+ characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
+
+ // Loosely modeled on CSS identifier characters
+ // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
+ // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+ identifier = characterEncoding.replace( "w", "w#" ),
+
+ // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
+ attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace +
+ // Operator (capture 2)
+ "*([*^$|!~]?=)" + whitespace +
+ // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
+ "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
+ "*\\]",
+
+ pseudos = ":(" + characterEncoding + ")(?:\\((" +
+ // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
+ // 1. quoted (capture 3; capture 4 or capture 5)
+ "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
+ // 2. simple (capture 6)
+ "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
+ // 3. anything else (capture 2)
+ ".*" +
+ ")\\)|)",
+
+ // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+ rwhitespace = new RegExp( whitespace + "+", "g" ),
+ rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
+
+ rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+ rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
+
+ rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
+
+ rpseudo = new RegExp( pseudos ),
+ ridentifier = new RegExp( "^" + identifier + "$" ),
+
+ matchExpr = {
+ "ID": new RegExp( "^#(" + characterEncoding + ")" ),
+ "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
+ "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
+ "ATTR": new RegExp( "^" + attributes ),
+ "PSEUDO": new RegExp( "^" + pseudos ),
+ "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
+ "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
+ "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+ "bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
+ // For use in libraries implementing .is()
+ // We use this for POS matching in `select`
+ "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
+ whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
+ },
+
+ rinputs = /^(?:input|select|textarea|button)$/i,
+ rheader = /^h\d$/i,
+
+ rnative = /^[^{]+\{\s*\[native \w/,
+
+ // Easily-parseable/retrievable ID or TAG or CLASS selectors
+ rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
+
+ rsibling = /[+~]/,
+ rescape = /'|\\/g,
+
+ // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
+ runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
+ funescape = function( _, escaped, escapedWhitespace ) {
+ var high = "0x" + escaped - 0x10000;
+ // NaN means non-codepoint
+ // Support: Firefox<24
+ // Workaround erroneous numeric interpretation of +"0x"
+ return high !== high || escapedWhitespace ?
+ escaped :
+ high < 0 ?
+ // BMP codepoint
+ String.fromCharCode( high + 0x10000 ) :
+ // Supplemental Plane codepoint (surrogate pair)
+ String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
+ },
+
+ // Used for iframes
+ // See setDocument()
+ // Removing the function wrapper causes a "Permission Denied"
+ // error in IE
+ unloadHandler = function() {
+ setDocument();
+ };
+
+// Optimize for push.apply( _, NodeList )
+try {
+ push.apply(
+ (arr = slice.call( preferredDoc.childNodes )),
+ preferredDoc.childNodes
+ );
+ // Support: Android<4.0
+ // Detect silently failing push.apply
+ arr[ preferredDoc.childNodes.length ].nodeType;
+} catch ( e ) {
+ push = { apply: arr.length ?
+
+ // Leverage slice if possible
+ function( target, els ) {
+ push_native.apply( target, slice.call(els) );
+ } :
+
+ // Support: IE<9
+ // Otherwise append directly
+ function( target, els ) {
+ var j = target.length,
+ i = 0;
+ // Can't trust NodeList.length
+ while ( (target[j++] = els[i++]) ) {}
+ target.length = j - 1;
+ }
+ };
+}
+
+function Sizzle( selector, context, results, seed ) {
+ var match, elem, m, nodeType,
+ // QSA vars
+ i, groups, old, nid, newContext, newSelector;
+
+ if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
+ setDocument( context );
+ }
+
+ context = context || document;
+ results = results || [];
+ nodeType = context.nodeType;
+
+ if ( typeof selector !== "string" || !selector ||
+ nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
+
+ return results;
+ }
+
+ if ( !seed && documentIsHTML ) {
+
+ // Try to shortcut find operations when possible (e.g., not under DocumentFragment)
+ if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
+ // Speed-up: Sizzle("#ID")
+ if ( (m = match[1]) ) {
+ if ( nodeType === 9 ) {
+ elem = context.getElementById( m );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document (jQuery #6963)
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE, Opera, and Webkit return items
+ // by name instead of ID
+ if ( elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ } else {
+ return results;
+ }
+ } else {
+ // Context is not a document
+ if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
+ contains( context, elem ) && elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ }
+
+ // Speed-up: Sizzle("TAG")
+ } else if ( match[2] ) {
+ push.apply( results, context.getElementsByTagName( selector ) );
+ return results;
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( (m = match[3]) && support.getElementsByClassName ) {
+ push.apply( results, context.getElementsByClassName( m ) );
+ return results;
+ }
+ }
+
+ // QSA path
+ if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+ nid = old = expando;
+ newContext = context;
+ newSelector = nodeType !== 1 && selector;
+
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ groups = tokenize( selector );
+
+ if ( (old = context.getAttribute("id")) ) {
+ nid = old.replace( rescape, "\\$&" );
+ } else {
+ context.setAttribute( "id", nid );
+ }
+ nid = "[id='" + nid + "'] ";
+
+ i = groups.length;
+ while ( i-- ) {
+ groups[i] = nid + toSelector( groups[i] );
+ }
+ newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
+ newSelector = groups.join(",");
+ }
+
+ if ( newSelector ) {
+ try {
+ push.apply( results,
+ newContext.querySelectorAll( newSelector )
+ );
+ return results;
+ } catch(qsaError) {
+ } finally {
+ if ( !old ) {
+ context.removeAttribute("id");
+ }
+ }
+ }
+ }
+ }
+
+ // All others
+ return select( selector.replace( rtrim, "$1" ), context, results, seed );
+}
+
+/**
+ * Create key-value caches of limited size
+ * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
+ * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
+ * deleting the oldest entry
+ */
+function createCache() {
+ var keys = [];
+
+ function cache( key, value ) {
+ // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
+ if ( keys.push( key + " " ) > Expr.cacheLength ) {
+ // Only keep the most recent entries
+ delete cache[ keys.shift() ];
+ }
+ return (cache[ key + " " ] = value);
+ }
+ return cache;
+}
+
+/**
+ * Mark a function for special use by Sizzle
+ * @param {Function} fn The function to mark
+ */
+function markFunction( fn ) {
+ fn[ expando ] = true;
+ return fn;
+}
+
+/**
+ * Support testing using an element
+ * @param {Function} fn Passed the created div and expects a boolean result
+ */
+function assert( fn ) {
+ var div = document.createElement("div");
+
+ try {
+ return !!fn( div );
+ } catch (e) {
+ return false;
+ } finally {
+ // Remove from its parent by default
+ if ( div.parentNode ) {
+ div.parentNode.removeChild( div );
+ }
+ // release memory in IE
+ div = null;
+ }
+}
+
+/**
+ * Adds the same handler for all of the specified attrs
+ * @param {String} attrs Pipe-separated list of attributes
+ * @param {Function} handler The method that will be applied
+ */
+function addHandle( attrs, handler ) {
+ var arr = attrs.split("|"),
+ i = attrs.length;
+
+ while ( i-- ) {
+ Expr.attrHandle[ arr[i] ] = handler;
+ }
+}
+
+/**
+ * Checks document order of two siblings
+ * @param {Element} a
+ * @param {Element} b
+ * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
+ */
+function siblingCheck( a, b ) {
+ var cur = b && a,
+ diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
+ ( ~b.sourceIndex || MAX_NEGATIVE ) -
+ ( ~a.sourceIndex || MAX_NEGATIVE );
+
+ // Use IE sourceIndex if available on both nodes
+ if ( diff ) {
+ return diff;
+ }
+
+ // Check if b follows a
+ if ( cur ) {
+ while ( (cur = cur.nextSibling) ) {
+ if ( cur === b ) {
+ return -1;
+ }
+ }
+ }
+
+ return a ? 1 : -1;
+}
+
+/**
+ * Returns a function to use in pseudos for input types
+ * @param {String} type
+ */
+function createInputPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === type;
+ };
+}
+
+/**
+ * Returns a function to use in pseudos for buttons
+ * @param {String} type
+ */
+function createButtonPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && elem.type === type;
+ };
+}
+
+/**
+ * Returns a function to use in pseudos for positionals
+ * @param {Function} fn
+ */
+function createPositionalPseudo( fn ) {
+ return markFunction(function( argument ) {
+ argument = +argument;
+ return markFunction(function( seed, matches ) {
+ var j,
+ matchIndexes = fn( [], seed.length, argument ),
+ i = matchIndexes.length;
+
+ // Match elements found at the specified indexes
+ while ( i-- ) {
+ if ( seed[ (j = matchIndexes[i]) ] ) {
+ seed[j] = !(matches[j] = seed[j]);
+ }
+ }
+ });
+ });
+}
+
+/**
+ * Checks a node for validity as a Sizzle context
+ * @param {Element|Object=} context
+ * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
+ */
+function testContext( context ) {
+ return context && typeof context.getElementsByTagName !== "undefined" && context;
+}
+
+// Expose support vars for convenience
+support = Sizzle.support = {};
+
+/**
+ * Detects XML nodes
+ * @param {Element|Object} elem An element or a document
+ * @returns {Boolean} True iff elem is a non-HTML XML node
+ */
+isXML = Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = elem && (elem.ownerDocument || elem).documentElement;
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+/**
+ * Sets document-related variables once based on the current document
+ * @param {Element|Object} [doc] An element or document object to use to set the document
+ * @returns {Object} Returns the current document
+ */
+setDocument = Sizzle.setDocument = function( node ) {
+ var hasCompare, parent,
+ doc = node ? node.ownerDocument || node : preferredDoc;
+
+ // If no document and documentElement is available, return
+ if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
+ return document;
+ }
+
+ // Set our document
+ document = doc;
+ docElem = doc.documentElement;
+ parent = doc.defaultView;
+
+ // Support: IE>8
+ // If iframe document is assigned to "document" variable and if iframe has been reloaded,
+ // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
+ // IE6-8 do not support the defaultView property so parent will be undefined
+ if ( parent && parent !== parent.top ) {
+ // IE11 does not have attachEvent, so all must suffer
+ if ( parent.addEventListener ) {
+ parent.addEventListener( "unload", unloadHandler, false );
+ } else if ( parent.attachEvent ) {
+ parent.attachEvent( "onunload", unloadHandler );
+ }
+ }
+
+ /* Support tests
+ ---------------------------------------------------------------------- */
+ documentIsHTML = !isXML( doc );
+
+ /* Attributes
+ ---------------------------------------------------------------------- */
+
+ // Support: IE<8
+ // Verify that getAttribute really returns attributes and not properties
+ // (excepting IE8 booleans)
+ support.attributes = assert(function( div ) {
+ div.className = "i";
+ return !div.getAttribute("className");
+ });
+
+ /* getElement(s)By*
+ ---------------------------------------------------------------------- */
+
+ // Check if getElementsByTagName("*") returns only elements
+ support.getElementsByTagName = assert(function( div ) {
+ div.appendChild( doc.createComment("") );
+ return !div.getElementsByTagName("*").length;
+ });
+
+ // Support: IE<9
+ support.getElementsByClassName = rnative.test( doc.getElementsByClassName );
+
+ // Support: IE<10
+ // Check if getElementById returns elements by name
+ // The broken getElementById methods don't pick up programatically-set names,
+ // so use a roundabout getElementsByName test
+ support.getById = assert(function( div ) {
+ docElem.appendChild( div ).id = expando;
+ return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
+ });
+
+ // ID find and filter
+ if ( support.getById ) {
+ Expr.find["ID"] = function( id, context ) {
+ if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
+ var m = context.getElementById( id );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [ m ] : [];
+ }
+ };
+ Expr.filter["ID"] = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ return elem.getAttribute("id") === attrId;
+ };
+ };
+ } else {
+ // Support: IE6/7
+ // getElementById is not reliable as a find shortcut
+ delete Expr.find["ID"];
+
+ Expr.filter["ID"] = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+ return node && node.value === attrId;
+ };
+ };
+ }
+
+ // Tag
+ Expr.find["TAG"] = support.getElementsByTagName ?
+ function( tag, context ) {
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ return context.getElementsByTagName( tag );
+
+ // DocumentFragment nodes don't have gEBTN
+ } else if ( support.qsa ) {
+ return context.querySelectorAll( tag );
+ }
+ } :
+
+ function( tag, context ) {
+ var elem,
+ tmp = [],
+ i = 0,
+ // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
+ results = context.getElementsByTagName( tag );
+
+ // Filter out possible comments
+ if ( tag === "*" ) {
+ while ( (elem = results[i++]) ) {
+ if ( elem.nodeType === 1 ) {
+ tmp.push( elem );
+ }
+ }
+
+ return tmp;
+ }
+ return results;
+ };
+
+ // Class
+ Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
+ if ( documentIsHTML ) {
+ return context.getElementsByClassName( className );
+ }
+ };
+
+ /* QSA/matchesSelector
+ ---------------------------------------------------------------------- */
+
+ // QSA and matchesSelector support
+
+ // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+ rbuggyMatches = [];
+
+ // qSa(:focus) reports false when true (Chrome 21)
+ // We allow this because of a bug in IE8/9 that throws an error
+ // whenever `document.activeElement` is accessed on an iframe
+ // So, we allow :focus to pass through QSA all the time to avoid the IE error
+ // See http://bugs.jquery.com/ticket/13378
+ rbuggyQSA = [];
+
+ if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
+ // Build QSA regex
+ // Regex strategy adopted from Diego Perini
+ assert(function( div ) {
+ // Select is set to empty string on purpose
+ // This is to test IE's treatment of not explicitly
+ // setting a boolean content attribute,
+ // since its presence should be enough
+ // http://bugs.jquery.com/ticket/12359
+ docElem.appendChild( div ).innerHTML = " " +
+ "" +
+ " ";
+
+ // Support: IE8, Opera 11-12.16
+ // Nothing should be selected when empty strings follow ^= or $= or *=
+ // The test attribute must be unknown in Opera but "safe" for WinRT
+ // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
+ if ( div.querySelectorAll("[msallowcapture^='']").length ) {
+ rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
+ }
+
+ // Support: IE8
+ // Boolean attributes and "value" are not treated correctly
+ if ( !div.querySelectorAll("[selected]").length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
+ }
+
+ // Support: Chrome<29, Android<4.2+, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.7+
+ if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
+ rbuggyQSA.push("~=");
+ }
+
+ // Webkit/Opera - :checked should return selected option elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ // IE8 throws error here and will not see later tests
+ if ( !div.querySelectorAll(":checked").length ) {
+ rbuggyQSA.push(":checked");
+ }
+
+ // Support: Safari 8+, iOS 8+
+ // https://bugs.webkit.org/show_bug.cgi?id=136851
+ // In-page `selector#id sibing-combinator selector` fails
+ if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) {
+ rbuggyQSA.push(".#.+[+~]");
+ }
+ });
+
+ assert(function( div ) {
+ // Support: Windows 8 Native Apps
+ // The type and name attributes are restricted during .innerHTML assignment
+ var input = doc.createElement("input");
+ input.setAttribute( "type", "hidden" );
+ div.appendChild( input ).setAttribute( "name", "D" );
+
+ // Support: IE8
+ // Enforce case-sensitivity of name attribute
+ if ( div.querySelectorAll("[name=d]").length ) {
+ rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
+ }
+
+ // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+ // IE8 throws error here and will not see later tests
+ if ( !div.querySelectorAll(":enabled").length ) {
+ rbuggyQSA.push( ":enabled", ":disabled" );
+ }
+
+ // Opera 10-11 does not throw on post-comma invalid pseudos
+ div.querySelectorAll("*,:x");
+ rbuggyQSA.push(",.*:");
+ });
+ }
+
+ if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
+ docElem.webkitMatchesSelector ||
+ docElem.mozMatchesSelector ||
+ docElem.oMatchesSelector ||
+ docElem.msMatchesSelector) )) ) {
+
+ assert(function( div ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9)
+ support.disconnectedMatch = matches.call( div, "div" );
+
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( div, "[s!='']:x" );
+ rbuggyMatches.push( "!=", pseudos );
+ });
+ }
+
+ rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
+ rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
+
+ /* Contains
+ ---------------------------------------------------------------------- */
+ hasCompare = rnative.test( docElem.compareDocumentPosition );
+
+ // Element contains another
+ // Purposefully does not implement inclusive descendent
+ // As in, an element does not contain itself
+ contains = hasCompare || rnative.test( docElem.contains ) ?
+ function( a, b ) {
+ var adown = a.nodeType === 9 ? a.documentElement : a,
+ bup = b && b.parentNode;
+ return a === bup || !!( bup && bup.nodeType === 1 && (
+ adown.contains ?
+ adown.contains( bup ) :
+ a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
+ ));
+ } :
+ function( a, b ) {
+ if ( b ) {
+ while ( (b = b.parentNode) ) {
+ if ( b === a ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ /* Sorting
+ ---------------------------------------------------------------------- */
+
+ // Document order sorting
+ sortOrder = hasCompare ?
+ function( a, b ) {
+
+ // Flag for duplicate removal
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ // Sort on method existence if only one input has compareDocumentPosition
+ var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
+ if ( compare ) {
+ return compare;
+ }
+
+ // Calculate position if both inputs belong to the same document
+ compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
+ a.compareDocumentPosition( b ) :
+
+ // Otherwise we know they are disconnected
+ 1;
+
+ // Disconnected nodes
+ if ( compare & 1 ||
+ (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
+
+ // Choose the first element that is related to our preferred document
+ if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
+ return -1;
+ }
+ if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
+ return 1;
+ }
+
+ // Maintain original order
+ return sortInput ?
+ ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+ 0;
+ }
+
+ return compare & 4 ? -1 : 1;
+ } :
+ function( a, b ) {
+ // Exit early if the nodes are identical
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ var cur,
+ i = 0,
+ aup = a.parentNode,
+ bup = b.parentNode,
+ ap = [ a ],
+ bp = [ b ];
+
+ // Parentless nodes are either documents or disconnected
+ if ( !aup || !bup ) {
+ return a === doc ? -1 :
+ b === doc ? 1 :
+ aup ? -1 :
+ bup ? 1 :
+ sortInput ?
+ ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+ 0;
+
+ // If the nodes are siblings, we can do a quick check
+ } else if ( aup === bup ) {
+ return siblingCheck( a, b );
+ }
+
+ // Otherwise we need full lists of their ancestors for comparison
+ cur = a;
+ while ( (cur = cur.parentNode) ) {
+ ap.unshift( cur );
+ }
+ cur = b;
+ while ( (cur = cur.parentNode) ) {
+ bp.unshift( cur );
+ }
+
+ // Walk down the tree looking for a discrepancy
+ while ( ap[i] === bp[i] ) {
+ i++;
+ }
+
+ return i ?
+ // Do a sibling check if the nodes have a common ancestor
+ siblingCheck( ap[i], bp[i] ) :
+
+ // Otherwise nodes in our document sort first
+ ap[i] === preferredDoc ? -1 :
+ bp[i] === preferredDoc ? 1 :
+ 0;
+ };
+
+ return doc;
+};
+
+Sizzle.matches = function( expr, elements ) {
+ return Sizzle( expr, null, null, elements );
+};
+
+Sizzle.matchesSelector = function( elem, expr ) {
+ // Set document vars if needed
+ if ( ( elem.ownerDocument || elem ) !== document ) {
+ setDocument( elem );
+ }
+
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace( rattributeQuotes, "='$1']" );
+
+ if ( support.matchesSelector && documentIsHTML &&
+ ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
+ ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
+
+ try {
+ var ret = matches.call( elem, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || support.disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9
+ elem.document && elem.document.nodeType !== 11 ) {
+ return ret;
+ }
+ } catch (e) {}
+ }
+
+ return Sizzle( expr, document, null, [ elem ] ).length > 0;
+};
+
+Sizzle.contains = function( context, elem ) {
+ // Set document vars if needed
+ if ( ( context.ownerDocument || context ) !== document ) {
+ setDocument( context );
+ }
+ return contains( context, elem );
+};
+
+Sizzle.attr = function( elem, name ) {
+ // Set document vars if needed
+ if ( ( elem.ownerDocument || elem ) !== document ) {
+ setDocument( elem );
+ }
+
+ var fn = Expr.attrHandle[ name.toLowerCase() ],
+ // Don't get fooled by Object.prototype properties (jQuery #13807)
+ val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
+ fn( elem, name, !documentIsHTML ) :
+ undefined;
+
+ return val !== undefined ?
+ val :
+ support.attributes || !documentIsHTML ?
+ elem.getAttribute( name ) :
+ (val = elem.getAttributeNode(name)) && val.specified ?
+ val.value :
+ null;
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Document sorting and removing duplicates
+ * @param {ArrayLike} results
+ */
+Sizzle.uniqueSort = function( results ) {
+ var elem,
+ duplicates = [],
+ j = 0,
+ i = 0;
+
+ // Unless we *know* we can detect duplicates, assume their presence
+ hasDuplicate = !support.detectDuplicates;
+ sortInput = !support.sortStable && results.slice( 0 );
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ while ( (elem = results[i++]) ) {
+ if ( elem === results[ i ] ) {
+ j = duplicates.push( i );
+ }
+ }
+ while ( j-- ) {
+ results.splice( duplicates[ j ], 1 );
+ }
+ }
+
+ // Clear input after sorting to release objects
+ // See https://github.com/jquery/sizzle/pull/225
+ sortInput = null;
+
+ return results;
+};
+
+/**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+getText = Sizzle.getText = function( elem ) {
+ var node,
+ ret = "",
+ i = 0,
+ nodeType = elem.nodeType;
+
+ if ( !nodeType ) {
+ // If no nodeType, this is expected to be an array
+ while ( (node = elem[i++]) ) {
+ // Do not traverse comment nodes
+ ret += getText( node );
+ }
+ } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+ // Use textContent for elements
+ // innerText usage removed for consistency of new lines (jQuery #11153)
+ if ( typeof elem.textContent === "string" ) {
+ return elem.textContent;
+ } else {
+ // Traverse its children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ // Do not include comment or processing instruction nodes
+
+ return ret;
+};
+
+Expr = Sizzle.selectors = {
+
+ // Can be adjusted by the user
+ cacheLength: 50,
+
+ createPseudo: markFunction,
+
+ match: matchExpr,
+
+ attrHandle: {},
+
+ find: {},
+
+ relative: {
+ ">": { dir: "parentNode", first: true },
+ " ": { dir: "parentNode" },
+ "+": { dir: "previousSibling", first: true },
+ "~": { dir: "previousSibling" }
+ },
+
+ preFilter: {
+ "ATTR": function( match ) {
+ match[1] = match[1].replace( runescape, funescape );
+
+ // Move the given value to match[3] whether quoted or unquoted
+ match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );
+
+ if ( match[2] === "~=" ) {
+ match[3] = " " + match[3] + " ";
+ }
+
+ return match.slice( 0, 4 );
+ },
+
+ "CHILD": function( match ) {
+ /* matches from matchExpr["CHILD"]
+ 1 type (only|nth|...)
+ 2 what (child|of-type)
+ 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+ 4 xn-component of xn+y argument ([+-]?\d*n|)
+ 5 sign of xn-component
+ 6 x of xn-component
+ 7 sign of y-component
+ 8 y of y-component
+ */
+ match[1] = match[1].toLowerCase();
+
+ if ( match[1].slice( 0, 3 ) === "nth" ) {
+ // nth-* requires argument
+ if ( !match[3] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // numeric x and y parameters for Expr.filter.CHILD
+ // remember that false/true cast respectively to 0/1
+ match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
+ match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
+
+ // other types prohibit arguments
+ } else if ( match[3] ) {
+ Sizzle.error( match[0] );
+ }
+
+ return match;
+ },
+
+ "PSEUDO": function( match ) {
+ var excess,
+ unquoted = !match[6] && match[2];
+
+ if ( matchExpr["CHILD"].test( match[0] ) ) {
+ return null;
+ }
+
+ // Accept quoted arguments as-is
+ if ( match[3] ) {
+ match[2] = match[4] || match[5] || "";
+
+ // Strip excess characters from unquoted arguments
+ } else if ( unquoted && rpseudo.test( unquoted ) &&
+ // Get excess from tokenize (recursively)
+ (excess = tokenize( unquoted, true )) &&
+ // advance to the next closing parenthesis
+ (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
+
+ // excess is a negative index
+ match[0] = match[0].slice( 0, excess );
+ match[2] = unquoted.slice( 0, excess );
+ }
+
+ // Return only captures needed by the pseudo filter method (type and argument)
+ return match.slice( 0, 3 );
+ }
+ },
+
+ filter: {
+
+ "TAG": function( nodeNameSelector ) {
+ var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
+ return nodeNameSelector === "*" ?
+ function() { return true; } :
+ function( elem ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+ };
+ },
+
+ "CLASS": function( className ) {
+ var pattern = classCache[ className + " " ];
+
+ return pattern ||
+ (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
+ classCache( className, function( elem ) {
+ return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" );
+ });
+ },
+
+ "ATTR": function( name, operator, check ) {
+ return function( elem ) {
+ var result = Sizzle.attr( elem, name );
+
+ if ( result == null ) {
+ return operator === "!=";
+ }
+ if ( !operator ) {
+ return true;
+ }
+
+ result += "";
+
+ return operator === "=" ? result === check :
+ operator === "!=" ? result !== check :
+ operator === "^=" ? check && result.indexOf( check ) === 0 :
+ operator === "*=" ? check && result.indexOf( check ) > -1 :
+ operator === "$=" ? check && result.slice( -check.length ) === check :
+ operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
+ operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
+ false;
+ };
+ },
+
+ "CHILD": function( type, what, argument, first, last ) {
+ var simple = type.slice( 0, 3 ) !== "nth",
+ forward = type.slice( -4 ) !== "last",
+ ofType = what === "of-type";
+
+ return first === 1 && last === 0 ?
+
+ // Shortcut for :nth-*(n)
+ function( elem ) {
+ return !!elem.parentNode;
+ } :
+
+ function( elem, context, xml ) {
+ var cache, outerCache, node, diff, nodeIndex, start,
+ dir = simple !== forward ? "nextSibling" : "previousSibling",
+ parent = elem.parentNode,
+ name = ofType && elem.nodeName.toLowerCase(),
+ useCache = !xml && !ofType;
+
+ if ( parent ) {
+
+ // :(first|last|only)-(child|of-type)
+ if ( simple ) {
+ while ( dir ) {
+ node = elem;
+ while ( (node = node[ dir ]) ) {
+ if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
+ return false;
+ }
+ }
+ // Reverse direction for :only-* (if we haven't yet done so)
+ start = dir = type === "only" && !start && "nextSibling";
+ }
+ return true;
+ }
+
+ start = [ forward ? parent.firstChild : parent.lastChild ];
+
+ // non-xml :nth-child(...) stores cache data on `parent`
+ if ( forward && useCache ) {
+ // Seek `elem` from a previously-cached index
+ outerCache = parent[ expando ] || (parent[ expando ] = {});
+ cache = outerCache[ type ] || [];
+ nodeIndex = cache[0] === dirruns && cache[1];
+ diff = cache[0] === dirruns && cache[2];
+ node = nodeIndex && parent.childNodes[ nodeIndex ];
+
+ while ( (node = ++nodeIndex && node && node[ dir ] ||
+
+ // Fallback to seeking `elem` from the start
+ (diff = nodeIndex = 0) || start.pop()) ) {
+
+ // When found, cache indexes on `parent` and break
+ if ( node.nodeType === 1 && ++diff && node === elem ) {
+ outerCache[ type ] = [ dirruns, nodeIndex, diff ];
+ break;
+ }
+ }
+
+ // Use previously-cached element index if available
+ } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
+ diff = cache[1];
+
+ // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
+ } else {
+ // Use the same loop as above to seek `elem` from the start
+ while ( (node = ++nodeIndex && node && node[ dir ] ||
+ (diff = nodeIndex = 0) || start.pop()) ) {
+
+ if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
+ // Cache the index of each encountered element
+ if ( useCache ) {
+ (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
+ }
+
+ if ( node === elem ) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Incorporate the offset, then check against cycle size
+ diff -= last;
+ return diff === first || ( diff % first === 0 && diff / first >= 0 );
+ }
+ };
+ },
+
+ "PSEUDO": function( pseudo, argument ) {
+ // pseudo-class names are case-insensitive
+ // http://www.w3.org/TR/selectors/#pseudo-classes
+ // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+ // Remember that setFilters inherits from pseudos
+ var args,
+ fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+ Sizzle.error( "unsupported pseudo: " + pseudo );
+
+ // The user may use createPseudo to indicate that
+ // arguments are needed to create the filter function
+ // just as Sizzle does
+ if ( fn[ expando ] ) {
+ return fn( argument );
+ }
+
+ // But maintain support for old signatures
+ if ( fn.length > 1 ) {
+ args = [ pseudo, pseudo, "", argument ];
+ return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+ markFunction(function( seed, matches ) {
+ var idx,
+ matched = fn( seed, argument ),
+ i = matched.length;
+ while ( i-- ) {
+ idx = indexOf( seed, matched[i] );
+ seed[ idx ] = !( matches[ idx ] = matched[i] );
+ }
+ }) :
+ function( elem ) {
+ return fn( elem, 0, args );
+ };
+ }
+
+ return fn;
+ }
+ },
+
+ pseudos: {
+ // Potentially complex pseudos
+ "not": markFunction(function( selector ) {
+ // Trim the selector passed to compile
+ // to avoid treating leading and trailing
+ // spaces as combinators
+ var input = [],
+ results = [],
+ matcher = compile( selector.replace( rtrim, "$1" ) );
+
+ return matcher[ expando ] ?
+ markFunction(function( seed, matches, context, xml ) {
+ var elem,
+ unmatched = matcher( seed, null, xml, [] ),
+ i = seed.length;
+
+ // Match elements unmatched by `matcher`
+ while ( i-- ) {
+ if ( (elem = unmatched[i]) ) {
+ seed[i] = !(matches[i] = elem);
+ }
+ }
+ }) :
+ function( elem, context, xml ) {
+ input[0] = elem;
+ matcher( input, null, xml, results );
+ // Don't keep the element (issue #299)
+ input[0] = null;
+ return !results.pop();
+ };
+ }),
+
+ "has": markFunction(function( selector ) {
+ return function( elem ) {
+ return Sizzle( selector, elem ).length > 0;
+ };
+ }),
+
+ "contains": markFunction(function( text ) {
+ text = text.replace( runescape, funescape );
+ return function( elem ) {
+ return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+ };
+ }),
+
+ // "Whether an element is represented by a :lang() selector
+ // is based solely on the element's language value
+ // being equal to the identifier C,
+ // or beginning with the identifier C immediately followed by "-".
+ // The matching of C against the element's language value is performed case-insensitively.
+ // The identifier C does not have to be a valid language name."
+ // http://www.w3.org/TR/selectors/#lang-pseudo
+ "lang": markFunction( function( lang ) {
+ // lang value must be a valid identifier
+ if ( !ridentifier.test(lang || "") ) {
+ Sizzle.error( "unsupported lang: " + lang );
+ }
+ lang = lang.replace( runescape, funescape ).toLowerCase();
+ return function( elem ) {
+ var elemLang;
+ do {
+ if ( (elemLang = documentIsHTML ?
+ elem.lang :
+ elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
+
+ elemLang = elemLang.toLowerCase();
+ return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
+ }
+ } while ( (elem = elem.parentNode) && elem.nodeType === 1 );
+ return false;
+ };
+ }),
+
+ // Miscellaneous
+ "target": function( elem ) {
+ var hash = window.location && window.location.hash;
+ return hash && hash.slice( 1 ) === elem.id;
+ },
+
+ "root": function( elem ) {
+ return elem === docElem;
+ },
+
+ "focus": function( elem ) {
+ return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
+ },
+
+ // Boolean properties
+ "enabled": function( elem ) {
+ return elem.disabled === false;
+ },
+
+ "disabled": function( elem ) {
+ return elem.disabled === true;
+ },
+
+ "checked": function( elem ) {
+ // In CSS3, :checked should return both checked and selected elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ var nodeName = elem.nodeName.toLowerCase();
+ return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
+ },
+
+ "selected": function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ // Contents
+ "empty": function( elem ) {
+ // http://www.w3.org/TR/selectors/#empty-pseudo
+ // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
+ // but not by others (comment: 8; processing instruction: 7; etc.)
+ // nodeType < 6 works because attributes (2) do not appear as children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ if ( elem.nodeType < 6 ) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ "parent": function( elem ) {
+ return !Expr.pseudos["empty"]( elem );
+ },
+
+ // Element/input types
+ "header": function( elem ) {
+ return rheader.test( elem.nodeName );
+ },
+
+ "input": function( elem ) {
+ return rinputs.test( elem.nodeName );
+ },
+
+ "button": function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === "button" || name === "button";
+ },
+
+ "text": function( elem ) {
+ var attr;
+ return elem.nodeName.toLowerCase() === "input" &&
+ elem.type === "text" &&
+
+ // Support: IE<8
+ // New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
+ ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
+ },
+
+ // Position-in-collection
+ "first": createPositionalPseudo(function() {
+ return [ 0 ];
+ }),
+
+ "last": createPositionalPseudo(function( matchIndexes, length ) {
+ return [ length - 1 ];
+ }),
+
+ "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ return [ argument < 0 ? argument + length : argument ];
+ }),
+
+ "even": createPositionalPseudo(function( matchIndexes, length ) {
+ var i = 0;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "odd": createPositionalPseudo(function( matchIndexes, length ) {
+ var i = 1;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ var i = argument < 0 ? argument + length : argument;
+ for ( ; --i >= 0; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ var i = argument < 0 ? argument + length : argument;
+ for ( ; ++i < length; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ })
+ }
+};
+
+Expr.pseudos["nth"] = Expr.pseudos["eq"];
+
+// Add button/input type pseudos
+for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
+ Expr.pseudos[ i ] = createInputPseudo( i );
+}
+for ( i in { submit: true, reset: true } ) {
+ Expr.pseudos[ i ] = createButtonPseudo( i );
+}
+
+// Easy API for creating new setFilters
+function setFilters() {}
+setFilters.prototype = Expr.filters = Expr.pseudos;
+Expr.setFilters = new setFilters();
+
+tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
+ var matched, match, tokens, type,
+ soFar, groups, preFilters,
+ cached = tokenCache[ selector + " " ];
+
+ if ( cached ) {
+ return parseOnly ? 0 : cached.slice( 0 );
+ }
+
+ soFar = selector;
+ groups = [];
+ preFilters = Expr.preFilter;
+
+ while ( soFar ) {
+
+ // Comma and first run
+ if ( !matched || (match = rcomma.exec( soFar )) ) {
+ if ( match ) {
+ // Don't consume trailing commas as valid
+ soFar = soFar.slice( match[0].length ) || soFar;
+ }
+ groups.push( (tokens = []) );
+ }
+
+ matched = false;
+
+ // Combinators
+ if ( (match = rcombinators.exec( soFar )) ) {
+ matched = match.shift();
+ tokens.push({
+ value: matched,
+ // Cast descendant combinators to space
+ type: match[0].replace( rtrim, " " )
+ });
+ soFar = soFar.slice( matched.length );
+ }
+
+ // Filters
+ for ( type in Expr.filter ) {
+ if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
+ (match = preFilters[ type ]( match ))) ) {
+ matched = match.shift();
+ tokens.push({
+ value: matched,
+ type: type,
+ matches: match
+ });
+ soFar = soFar.slice( matched.length );
+ }
+ }
+
+ if ( !matched ) {
+ break;
+ }
+ }
+
+ // Return the length of the invalid excess
+ // if we're just parsing
+ // Otherwise, throw an error or return tokens
+ return parseOnly ?
+ soFar.length :
+ soFar ?
+ Sizzle.error( selector ) :
+ // Cache the tokens
+ tokenCache( selector, groups ).slice( 0 );
+};
+
+function toSelector( tokens ) {
+ var i = 0,
+ len = tokens.length,
+ selector = "";
+ for ( ; i < len; i++ ) {
+ selector += tokens[i].value;
+ }
+ return selector;
+}
+
+function addCombinator( matcher, combinator, base ) {
+ var dir = combinator.dir,
+ checkNonElements = base && dir === "parentNode",
+ doneName = done++;
+
+ return combinator.first ?
+ // Check against closest ancestor/preceding element
+ function( elem, context, xml ) {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ return matcher( elem, context, xml );
+ }
+ }
+ } :
+
+ // Check against all ancestor/preceding elements
+ function( elem, context, xml ) {
+ var oldCache, outerCache,
+ newCache = [ dirruns, doneName ];
+
+ // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
+ if ( xml ) {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ if ( matcher( elem, context, xml ) ) {
+ return true;
+ }
+ }
+ }
+ } else {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ outerCache = elem[ expando ] || (elem[ expando ] = {});
+ if ( (oldCache = outerCache[ dir ]) &&
+ oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
+
+ // Assign to newCache so results back-propagate to previous elements
+ return (newCache[ 2 ] = oldCache[ 2 ]);
+ } else {
+ // Reuse newcache so results back-propagate to previous elements
+ outerCache[ dir ] = newCache;
+
+ // A match means we're done; a fail means we have to keep checking
+ if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ };
+}
+
+function elementMatcher( matchers ) {
+ return matchers.length > 1 ?
+ function( elem, context, xml ) {
+ var i = matchers.length;
+ while ( i-- ) {
+ if ( !matchers[i]( elem, context, xml ) ) {
+ return false;
+ }
+ }
+ return true;
+ } :
+ matchers[0];
+}
+
+function multipleContexts( selector, contexts, results ) {
+ var i = 0,
+ len = contexts.length;
+ for ( ; i < len; i++ ) {
+ Sizzle( selector, contexts[i], results );
+ }
+ return results;
+}
+
+function condense( unmatched, map, filter, context, xml ) {
+ var elem,
+ newUnmatched = [],
+ i = 0,
+ len = unmatched.length,
+ mapped = map != null;
+
+ for ( ; i < len; i++ ) {
+ if ( (elem = unmatched[i]) ) {
+ if ( !filter || filter( elem, context, xml ) ) {
+ newUnmatched.push( elem );
+ if ( mapped ) {
+ map.push( i );
+ }
+ }
+ }
+ }
+
+ return newUnmatched;
+}
+
+function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+ if ( postFilter && !postFilter[ expando ] ) {
+ postFilter = setMatcher( postFilter );
+ }
+ if ( postFinder && !postFinder[ expando ] ) {
+ postFinder = setMatcher( postFinder, postSelector );
+ }
+ return markFunction(function( seed, results, context, xml ) {
+ var temp, i, elem,
+ preMap = [],
+ postMap = [],
+ preexisting = results.length,
+
+ // Get initial elements from seed or context
+ elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
+
+ // Prefilter to get matcher input, preserving a map for seed-results synchronization
+ matcherIn = preFilter && ( seed || !selector ) ?
+ condense( elems, preMap, preFilter, context, xml ) :
+ elems,
+
+ matcherOut = matcher ?
+ // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+ postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+ // ...intermediate processing is necessary
+ [] :
+
+ // ...otherwise use results directly
+ results :
+ matcherIn;
+
+ // Find primary matches
+ if ( matcher ) {
+ matcher( matcherIn, matcherOut, context, xml );
+ }
+
+ // Apply postFilter
+ if ( postFilter ) {
+ temp = condense( matcherOut, postMap );
+ postFilter( temp, [], context, xml );
+
+ // Un-match failing elements by moving them back to matcherIn
+ i = temp.length;
+ while ( i-- ) {
+ if ( (elem = temp[i]) ) {
+ matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
+ }
+ }
+ }
+
+ if ( seed ) {
+ if ( postFinder || preFilter ) {
+ if ( postFinder ) {
+ // Get the final matcherOut by condensing this intermediate into postFinder contexts
+ temp = [];
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) ) {
+ // Restore matcherIn since elem is not yet a final match
+ temp.push( (matcherIn[i] = elem) );
+ }
+ }
+ postFinder( null, (matcherOut = []), temp, xml );
+ }
+
+ // Move matched elements from seed to results to keep them synchronized
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) &&
+ (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {
+
+ seed[temp] = !(results[temp] = elem);
+ }
+ }
+ }
+
+ // Add elements to results, through postFinder if defined
+ } else {
+ matcherOut = condense(
+ matcherOut === results ?
+ matcherOut.splice( preexisting, matcherOut.length ) :
+ matcherOut
+ );
+ if ( postFinder ) {
+ postFinder( null, results, matcherOut, xml );
+ } else {
+ push.apply( results, matcherOut );
+ }
+ }
+ });
+}
+
+function matcherFromTokens( tokens ) {
+ var checkContext, matcher, j,
+ len = tokens.length,
+ leadingRelative = Expr.relative[ tokens[0].type ],
+ implicitRelative = leadingRelative || Expr.relative[" "],
+ i = leadingRelative ? 1 : 0,
+
+ // The foundational matcher ensures that elements are reachable from top-level context(s)
+ matchContext = addCombinator( function( elem ) {
+ return elem === checkContext;
+ }, implicitRelative, true ),
+ matchAnyContext = addCombinator( function( elem ) {
+ return indexOf( checkContext, elem ) > -1;
+ }, implicitRelative, true ),
+ matchers = [ function( elem, context, xml ) {
+ var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+ (checkContext = context).nodeType ?
+ matchContext( elem, context, xml ) :
+ matchAnyContext( elem, context, xml ) );
+ // Avoid hanging onto element (issue #299)
+ checkContext = null;
+ return ret;
+ } ];
+
+ for ( ; i < len; i++ ) {
+ if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
+ matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
+ } else {
+ matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
+
+ // Return special upon seeing a positional matcher
+ if ( matcher[ expando ] ) {
+ // Find the next relative operator (if any) for proper handling
+ j = ++i;
+ for ( ; j < len; j++ ) {
+ if ( Expr.relative[ tokens[j].type ] ) {
+ break;
+ }
+ }
+ return setMatcher(
+ i > 1 && elementMatcher( matchers ),
+ i > 1 && toSelector(
+ // If the preceding token was a descendant combinator, insert an implicit any-element `*`
+ tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
+ ).replace( rtrim, "$1" ),
+ matcher,
+ i < j && matcherFromTokens( tokens.slice( i, j ) ),
+ j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
+ j < len && toSelector( tokens )
+ );
+ }
+ matchers.push( matcher );
+ }
+ }
+
+ return elementMatcher( matchers );
+}
+
+function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+ var bySet = setMatchers.length > 0,
+ byElement = elementMatchers.length > 0,
+ superMatcher = function( seed, context, xml, results, outermost ) {
+ var elem, j, matcher,
+ matchedCount = 0,
+ i = "0",
+ unmatched = seed && [],
+ setMatched = [],
+ contextBackup = outermostContext,
+ // We must always have either seed elements or outermost context
+ elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
+ // Use integer dirruns iff this is the outermost matcher
+ dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
+ len = elems.length;
+
+ if ( outermost ) {
+ outermostContext = context !== document && context;
+ }
+
+ // Add elements passing elementMatchers directly to results
+ // Keep `i` a string if there are no elements so `matchedCount` will be "00" below
+ // Support: IE<9, Safari
+ // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id
+ for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
+ if ( byElement && elem ) {
+ j = 0;
+ while ( (matcher = elementMatchers[j++]) ) {
+ if ( matcher( elem, context, xml ) ) {
+ results.push( elem );
+ break;
+ }
+ }
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ }
+ }
+
+ // Track unmatched elements for set filters
+ if ( bySet ) {
+ // They will have gone through all possible matchers
+ if ( (elem = !matcher && elem) ) {
+ matchedCount--;
+ }
+
+ // Lengthen the array for every element, matched or not
+ if ( seed ) {
+ unmatched.push( elem );
+ }
+ }
+ }
+
+ // Apply set filters to unmatched elements
+ matchedCount += i;
+ if ( bySet && i !== matchedCount ) {
+ j = 0;
+ while ( (matcher = setMatchers[j++]) ) {
+ matcher( unmatched, setMatched, context, xml );
+ }
+
+ if ( seed ) {
+ // Reintegrate element matches to eliminate the need for sorting
+ if ( matchedCount > 0 ) {
+ while ( i-- ) {
+ if ( !(unmatched[i] || setMatched[i]) ) {
+ setMatched[i] = pop.call( results );
+ }
+ }
+ }
+
+ // Discard index placeholder values to get only actual matches
+ setMatched = condense( setMatched );
+ }
+
+ // Add matches to results
+ push.apply( results, setMatched );
+
+ // Seedless set matches succeeding multiple successful matchers stipulate sorting
+ if ( outermost && !seed && setMatched.length > 0 &&
+ ( matchedCount + setMatchers.length ) > 1 ) {
+
+ Sizzle.uniqueSort( results );
+ }
+ }
+
+ // Override manipulation of globals by nested matchers
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ outermostContext = contextBackup;
+ }
+
+ return unmatched;
+ };
+
+ return bySet ?
+ markFunction( superMatcher ) :
+ superMatcher;
+}
+
+compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
+ var i,
+ setMatchers = [],
+ elementMatchers = [],
+ cached = compilerCache[ selector + " " ];
+
+ if ( !cached ) {
+ // Generate a function of recursive functions that can be used to check each element
+ if ( !match ) {
+ match = tokenize( selector );
+ }
+ i = match.length;
+ while ( i-- ) {
+ cached = matcherFromTokens( match[i] );
+ if ( cached[ expando ] ) {
+ setMatchers.push( cached );
+ } else {
+ elementMatchers.push( cached );
+ }
+ }
+
+ // Cache the compiled function
+ cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
+
+ // Save selector and tokenization
+ cached.selector = selector;
+ }
+ return cached;
+};
+
+/**
+ * A low-level selection function that works with Sizzle's compiled
+ * selector functions
+ * @param {String|Function} selector A selector or a pre-compiled
+ * selector function built with Sizzle.compile
+ * @param {Element} context
+ * @param {Array} [results]
+ * @param {Array} [seed] A set of elements to match against
+ */
+select = Sizzle.select = function( selector, context, results, seed ) {
+ var i, tokens, token, type, find,
+ compiled = typeof selector === "function" && selector,
+ match = !seed && tokenize( (selector = compiled.selector || selector) );
+
+ results = results || [];
+
+ // Try to minimize operations if there is no seed and only one group
+ if ( match.length === 1 ) {
+
+ // Take a shortcut and set the context if the root selector is an ID
+ tokens = match[0] = match[0].slice( 0 );
+ if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
+ support.getById && context.nodeType === 9 && documentIsHTML &&
+ Expr.relative[ tokens[1].type ] ) {
+
+ context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
+ if ( !context ) {
+ return results;
+
+ // Precompiled matchers will still verify ancestry, so step up a level
+ } else if ( compiled ) {
+ context = context.parentNode;
+ }
+
+ selector = selector.slice( tokens.shift().value.length );
+ }
+
+ // Fetch a seed set for right-to-left matching
+ i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
+ while ( i-- ) {
+ token = tokens[i];
+
+ // Abort if we hit a combinator
+ if ( Expr.relative[ (type = token.type) ] ) {
+ break;
+ }
+ if ( (find = Expr.find[ type ]) ) {
+ // Search, expanding context for leading sibling combinators
+ if ( (seed = find(
+ token.matches[0].replace( runescape, funescape ),
+ rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
+ )) ) {
+
+ // If seed is empty or no tokens remain, we can return early
+ tokens.splice( i, 1 );
+ selector = seed.length && toSelector( tokens );
+ if ( !selector ) {
+ push.apply( results, seed );
+ return results;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ // Compile and execute a filtering function if one is not provided
+ // Provide `match` to avoid retokenization if we modified the selector above
+ ( compiled || compile( selector, match ) )(
+ seed,
+ context,
+ !documentIsHTML,
+ results,
+ rsibling.test( selector ) && testContext( context.parentNode ) || context
+ );
+ return results;
+};
+
+// One-time assignments
+
+// Sort stability
+support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
+
+// Support: Chrome 14-35+
+// Always assume duplicates if they aren't passed to the comparison function
+support.detectDuplicates = !!hasDuplicate;
+
+// Initialize against the default document
+setDocument();
+
+// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
+// Detached nodes confoundingly follow *each other*
+support.sortDetached = assert(function( div1 ) {
+ // Should return 1, but returns 4 (following)
+ return div1.compareDocumentPosition( document.createElement("div") ) & 1;
+});
+
+// Support: IE<8
+// Prevent attribute/property "interpolation"
+// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+if ( !assert(function( div ) {
+ div.innerHTML = " ";
+ return div.firstChild.getAttribute("href") === "#" ;
+}) ) {
+ addHandle( "type|href|height|width", function( elem, name, isXML ) {
+ if ( !isXML ) {
+ return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
+ }
+ });
+}
+
+// Support: IE<9
+// Use defaultValue in place of getAttribute("value")
+if ( !support.attributes || !assert(function( div ) {
+ div.innerHTML = " ";
+ div.firstChild.setAttribute( "value", "" );
+ return div.firstChild.getAttribute( "value" ) === "";
+}) ) {
+ addHandle( "value", function( elem, name, isXML ) {
+ if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
+ return elem.defaultValue;
+ }
+ });
+}
+
+// Support: IE<9
+// Use getAttributeNode to fetch booleans when getAttribute lies
+if ( !assert(function( div ) {
+ return div.getAttribute("disabled") == null;
+}) ) {
+ addHandle( booleans, function( elem, name, isXML ) {
+ var val;
+ if ( !isXML ) {
+ return elem[ name ] === true ? name.toLowerCase() :
+ (val = elem.getAttributeNode( name )) && val.specified ?
+ val.value :
+ null;
+ }
+ });
+}
+
+return Sizzle;
+
+})( window );
+
+
+
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.pseudos;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+
+var rneedsContext = jQuery.expr.match.needsContext;
+
+var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/);
+
+
+
+var risSimple = /^.[^:#\[\.,]*$/;
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, not ) {
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep( elements, function( elem, i ) {
+ /* jshint -W018 */
+ return !!qualifier.call( elem, i, elem ) !== not;
+ });
+
+ }
+
+ if ( qualifier.nodeType ) {
+ return jQuery.grep( elements, function( elem ) {
+ return ( elem === qualifier ) !== not;
+ });
+
+ }
+
+ if ( typeof qualifier === "string" ) {
+ if ( risSimple.test( qualifier ) ) {
+ return jQuery.filter( qualifier, elements, not );
+ }
+
+ qualifier = jQuery.filter( qualifier, elements );
+ }
+
+ return jQuery.grep( elements, function( elem ) {
+ return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not;
+ });
+}
+
+jQuery.filter = function( expr, elems, not ) {
+ var elem = elems[ 0 ];
+
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return elems.length === 1 && elem.nodeType === 1 ?
+ jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
+ jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
+ return elem.nodeType === 1;
+ }));
+};
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var i,
+ ret = [],
+ self = this,
+ len = self.length;
+
+ if ( typeof selector !== "string" ) {
+ return this.pushStack( jQuery( selector ).filter(function() {
+ for ( i = 0; i < len; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ }) );
+ }
+
+ for ( i = 0; i < len; i++ ) {
+ jQuery.find( selector, self[ i ], ret );
+ }
+
+ // Needed because $( selector, context ) becomes $( context ).find( selector )
+ ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
+ ret.selector = this.selector ? this.selector + " " + selector : selector;
+ return ret;
+ },
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector || [], false) );
+ },
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector || [], true) );
+ },
+ is: function( selector ) {
+ return !!winnow(
+ this,
+
+ // If this is a positional/relative selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ typeof selector === "string" && rneedsContext.test( selector ) ?
+ jQuery( selector ) :
+ selector || [],
+ false
+ ).length;
+ }
+});
+
+
+// Initialize a jQuery object
+
+
+// A central reference to the root jQuery(document)
+var rootjQuery,
+
+ // Use the correct document accordingly with window argument (sandbox)
+ document = window.document,
+
+ // A simple way to check for HTML strings
+ // Prioritize #id over to avoid XSS via location.hash (#9521)
+ // Strict HTML recognition (#11290: must start with <)
+ rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
+
+ init = jQuery.fn.init = function( selector, context ) {
+ var match, elem;
+
+ // HANDLE: $(""), $(null), $(undefined), $(false)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = rquickExpr.exec( selector );
+ }
+
+ // Match html or make sure no context is specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+
+ // scripts is true for back-compat
+ // Intentionally let the error be thrown if parseHTML is not present
+ jQuery.merge( this, jQuery.parseHTML(
+ match[1],
+ context && context.nodeType ? context.ownerDocument || context : document,
+ true
+ ) );
+
+ // HANDLE: $(html, props)
+ if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+ for ( match in context ) {
+ // Properties of context are called as methods if possible
+ if ( jQuery.isFunction( this[ match ] ) ) {
+ this[ match ]( context[ match ] );
+
+ // ...and otherwise set as attributes
+ } else {
+ this.attr( match, context[ match ] );
+ }
+ }
+ }
+
+ return this;
+
+ // HANDLE: $(#id)
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(DOMElement)
+ } else if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return typeof rootjQuery.ready !== "undefined" ?
+ rootjQuery.ready( selector ) :
+ // Execute immediately if ready is not present
+ selector( jQuery );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ };
+
+// Give the init function the jQuery prototype for later instantiation
+init.prototype = jQuery.fn;
+
+// Initialize central reference
+rootjQuery = jQuery( document );
+
+
+var rparentsprev = /^(?:parents|prev(?:Until|All))/,
+ // methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.extend({
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ cur = elem[ dir ];
+
+ while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+ if ( cur.nodeType === 1 ) {
+ matched.push( cur );
+ }
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ r.push( n );
+ }
+ }
+
+ return r;
+ }
+});
+
+jQuery.fn.extend({
+ has: function( target ) {
+ var i,
+ targets = jQuery( target, this ),
+ len = targets.length;
+
+ return this.filter(function() {
+ for ( i = 0; i < len; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ closest: function( selectors, context ) {
+ var cur,
+ i = 0,
+ l = this.length,
+ matched = [],
+ pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
+ jQuery( selectors, context || this.context ) :
+ 0;
+
+ for ( ; i < l; i++ ) {
+ for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {
+ // Always skip document fragments
+ if ( cur.nodeType < 11 && (pos ?
+ pos.index(cur) > -1 :
+
+ // Don't pass non-elements to Sizzle
+ cur.nodeType === 1 &&
+ jQuery.find.matchesSelector(cur, selectors)) ) {
+
+ matched.push( cur );
+ break;
+ }
+ }
+ }
+
+ return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1;
+ }
+
+ // index in selector
+ if ( typeof elem === "string" ) {
+ return jQuery.inArray( this[0], jQuery( elem ) );
+ }
+
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[0] : elem, this );
+ },
+
+ add: function( selector, context ) {
+ return this.pushStack(
+ jQuery.unique(
+ jQuery.merge( this.get(), jQuery( selector, context ) )
+ )
+ );
+ },
+
+ addBack: function( selector ) {
+ return this.add( selector == null ?
+ this.prevObject : this.prevObject.filter(selector)
+ );
+ }
+});
+
+function sibling( cur, dir ) {
+ do {
+ cur = cur[ dir ];
+ } while ( cur && cur.nodeType !== 1 );
+
+ return cur;
+}
+
+jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return sibling( elem, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return sibling( elem, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return jQuery.nodeName( elem, "iframe" ) ?
+ elem.contentDocument || elem.contentWindow.document :
+ jQuery.merge( [], elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var ret = jQuery.map( this, fn, until );
+
+ if ( name.slice( -5 ) !== "Until" ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ ret = jQuery.filter( selector, ret );
+ }
+
+ if ( this.length > 1 ) {
+ // Remove duplicates
+ if ( !guaranteedUnique[ name ] ) {
+ ret = jQuery.unique( ret );
+ }
+
+ // Reverse order for parents* and prev-derivatives
+ if ( rparentsprev.test( name ) ) {
+ ret = ret.reverse();
+ }
+ }
+
+ return this.pushStack( ret );
+ };
+});
+var rnotwhite = (/\S+/g);
+
+
+
+// String to Object options format cache
+var optionsCache = {};
+
+// Convert String-formatted options into Object-formatted ones and store in cache
+function createOptions( options ) {
+ var object = optionsCache[ options ] = {};
+ jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
+ object[ flag ] = true;
+ });
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * options: an optional list of space-separated options that will change how
+ * the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ ( optionsCache[ options ] || createOptions( options ) ) :
+ jQuery.extend( {}, options );
+
+ var // Flag to know if list is currently firing
+ firing,
+ // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list was already fired
+ fired,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = !options.once && [],
+ // Fire callbacks
+ fire = function( data ) {
+ memory = options.memory && data;
+ fired = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ firing = true;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+ memory = false; // To prevent further calls using add
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( stack ) {
+ if ( stack.length ) {
+ fire( stack.shift() );
+ }
+ } else if ( memory ) {
+ list = [];
+ } else {
+ self.disable();
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ // First, we save the current length
+ var start = list.length;
+ (function add( args ) {
+ jQuery.each( args, function( _, arg ) {
+ var type = jQuery.type( arg );
+ if ( type === "function" ) {
+ if ( !options.unique || !self.has( arg ) ) {
+ list.push( arg );
+ }
+ } else if ( arg && arg.length && type !== "string" ) {
+ // Inspect recursively
+ add( arg );
+ }
+ });
+ })( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away
+ } else if ( memory ) {
+ firingStart = start;
+ fire( memory );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ jQuery.each( arguments, function( _, arg ) {
+ var index;
+ while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+ // Handle firing indexes
+ if ( firing ) {
+ if ( index <= firingLength ) {
+ firingLength--;
+ }
+ if ( index <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ });
+ }
+ return this;
+ },
+ // Check if a given callback is in the list.
+ // If no argument is given, return whether or not list has callbacks attached.
+ has: function( fn ) {
+ return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ firingLength = 0;
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ if ( list && ( !fired || stack ) ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ if ( firing ) {
+ stack.push( args );
+ } else {
+ fire( args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+};
+
+
+jQuery.extend({
+
+ Deferred: function( func ) {
+ var tuples = [
+ // action, add listener, listener list, final state
+ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
+ [ "notify", "progress", jQuery.Callbacks("memory") ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
+ },
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
+ },
+ then: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( tuples, function( i, tuple ) {
+ var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
+ // deferred[ done | fail | progress ] for forwarding actions to newDefer
+ deferred[ tuple[1] ](function() {
+ var returned = fn && fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise()
+ .done( newDefer.resolve )
+ .fail( newDefer.reject )
+ .progress( newDefer.notify );
+ } else {
+ newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
+ }
+ });
+ });
+ fns = null;
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return obj != null ? jQuery.extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
+
+ // Keep pipe for back-compat
+ promise.pipe = promise.then;
+
+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 3 ];
+
+ // promise[ done | fail | progress ] = list.add
+ promise[ tuple[1] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(function() {
+ // state = [ resolved | rejected ]
+ state = stateString;
+
+ // [ reject_list | resolve_list ].disable; progress_list.lock
+ }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+ }
+
+ // deferred[ resolve | reject | notify ]
+ deferred[ tuple[0] ] = function() {
+ deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
+ return this;
+ };
+ deferred[ tuple[0] + "With" ] = list.fireWith;
+ });
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( subordinate /* , ..., subordinateN */ ) {
+ var i = 0,
+ resolveValues = slice.call( arguments ),
+ length = resolveValues.length,
+
+ // the count of uncompleted subordinates
+ remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+ // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+ deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+ // Update function for both resolve and progress values
+ updateFunc = function( i, contexts, values ) {
+ return function( value ) {
+ contexts[ i ] = this;
+ values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
+ if ( values === progressValues ) {
+ deferred.notifyWith( contexts, values );
+
+ } else if ( !(--remaining) ) {
+ deferred.resolveWith( contexts, values );
+ }
+ };
+ },
+
+ progressValues, progressContexts, resolveContexts;
+
+ // add listeners to Deferred subordinates; treat others as resolved
+ if ( length > 1 ) {
+ progressValues = new Array( length );
+ progressContexts = new Array( length );
+ resolveContexts = new Array( length );
+ for ( ; i < length; i++ ) {
+ if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+ resolveValues[ i ].promise()
+ .done( updateFunc( i, resolveContexts, resolveValues ) )
+ .fail( deferred.reject )
+ .progress( updateFunc( i, progressContexts, progressValues ) );
+ } else {
+ --remaining;
+ }
+ }
+ }
+
+ // if we're not waiting on anything, resolve the master
+ if ( !remaining ) {
+ deferred.resolveWith( resolveContexts, resolveValues );
+ }
+
+ return deferred.promise();
+ }
+});
+
+
+// The deferred used on DOM ready
+var readyList;
+
+jQuery.fn.ready = function( fn ) {
+ // Add the callback
+ jQuery.ready.promise().done( fn );
+
+ return this;
+};
+
+jQuery.extend({
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+
+ // Abort if there are pending holds or we're already ready
+ if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+ return;
+ }
+
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready );
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.triggerHandler ) {
+ jQuery( document ).triggerHandler( "ready" );
+ jQuery( document ).off( "ready" );
+ }
+ }
+});
+
+/**
+ * Clean-up method for dom ready events
+ */
+function detach() {
+ if ( document.addEventListener ) {
+ document.removeEventListener( "DOMContentLoaded", completed, false );
+ window.removeEventListener( "load", completed, false );
+
+ } else {
+ document.detachEvent( "onreadystatechange", completed );
+ window.detachEvent( "onload", completed );
+ }
+}
+
+/**
+ * The ready event handler and self cleanup method
+ */
+function completed() {
+ // readyState === "complete" is good enough for us to call the dom ready in oldIE
+ if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) {
+ detach();
+ jQuery.ready();
+ }
+}
+
+jQuery.ready.promise = function( obj ) {
+ if ( !readyList ) {
+
+ readyList = jQuery.Deferred();
+
+ // Catch cases where $(document).ready() is called after the browser event has already occurred.
+ // we once tried to use readyState "interactive" here, but it caused issues like the one
+ // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ setTimeout( jQuery.ready );
+
+ // Standards-based browsers support DOMContentLoaded
+ } else if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", completed, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", completed, false );
+
+ // If IE event model is used
+ } else {
+ // Ensure firing before onload, maybe late but safe also for iframes
+ document.attachEvent( "onreadystatechange", completed );
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", completed );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var top = false;
+
+ try {
+ top = window.frameElement == null && document.documentElement;
+ } catch(e) {}
+
+ if ( top && top.doScroll ) {
+ (function doScrollCheck() {
+ if ( !jQuery.isReady ) {
+
+ try {
+ // Use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ top.doScroll("left");
+ } catch(e) {
+ return setTimeout( doScrollCheck, 50 );
+ }
+
+ // detach all dom ready events
+ detach();
+
+ // and execute any waiting functions
+ jQuery.ready();
+ }
+ })();
+ }
+ }
+ }
+ return readyList.promise( obj );
+};
+
+
+var strundefined = typeof undefined;
+
+
+
+// Support: IE<9
+// Iteration over object's inherited properties before its own
+var i;
+for ( i in jQuery( support ) ) {
+ break;
+}
+support.ownLast = i !== "0";
+
+// Note: most support tests are defined in their respective modules.
+// false until the test is run
+support.inlineBlockNeedsLayout = false;
+
+// Execute ASAP in case we need to set body.style.zoom
+jQuery(function() {
+ // Minified: var a,b,c,d
+ var val, div, body, container;
+
+ body = document.getElementsByTagName( "body" )[ 0 ];
+ if ( !body || !body.style ) {
+ // Return for frameset docs that don't have a body
+ return;
+ }
+
+ // Setup
+ div = document.createElement( "div" );
+ container = document.createElement( "div" );
+ container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px";
+ body.appendChild( container ).appendChild( div );
+
+ if ( typeof div.style.zoom !== strundefined ) {
+ // Support: IE<8
+ // Check if natively block-level elements act like inline-block
+ // elements when setting their display to 'inline' and giving
+ // them layout
+ div.style.cssText = "display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1";
+
+ support.inlineBlockNeedsLayout = val = div.offsetWidth === 3;
+ if ( val ) {
+ // Prevent IE 6 from affecting layout for positioned elements #11048
+ // Prevent IE from shrinking the body in IE 7 mode #12869
+ // Support: IE<8
+ body.style.zoom = 1;
+ }
+ }
+
+ body.removeChild( container );
+});
+
+
+
+
+(function() {
+ var div = document.createElement( "div" );
+
+ // Execute the test only if not already executed in another module.
+ if (support.deleteExpando == null) {
+ // Support: IE<9
+ support.deleteExpando = true;
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
+ }
+ }
+
+ // Null elements to avoid leaks in IE.
+ div = null;
+})();
+
+
+/**
+ * Determines whether an object can have data
+ */
+jQuery.acceptData = function( elem ) {
+ var noData = jQuery.noData[ (elem.nodeName + " ").toLowerCase() ],
+ nodeType = +elem.nodeType || 1;
+
+ // Do not set data on non-element DOM nodes because it will not be cleared (#8335).
+ return nodeType !== 1 && nodeType !== 9 ?
+ false :
+
+ // Nodes accept data unless otherwise specified; rejection can be conditional
+ !noData || noData !== true && elem.getAttribute("classid") === noData;
+};
+
+
+var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+function dataAttr( elem, key, data ) {
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+
+ var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ // Only convert to a number if it doesn't change the string
+ +data + "" === data ? +data :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
+
+ } else {
+ data = undefined;
+ }
+ }
+
+ return data;
+}
+
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+ var name;
+ for ( name in obj ) {
+
+ // if the public data object is empty, the private is still empty
+ if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+ continue;
+ }
+ if ( name !== "toJSON" ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+function internalData( elem, name, data, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var ret, thisCache,
+ internalKey = jQuery.expando,
+
+ // We have to handle DOM nodes and JS objects differently because IE6-7
+ // can't GC object references properly across the DOM-JS boundary
+ isNode = elem.nodeType,
+
+ // Only DOM nodes need the global jQuery cache; JS object data is
+ // attached directly to the object so GC can occur automatically
+ cache = isNode ? jQuery.cache : elem,
+
+ // Only defining an ID for JS objects if its cache already exists allows
+ // the code to shortcut on the same path as a DOM node with no cache
+ id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
+
+ // Avoid doing any more work than we need to when trying to get data on an
+ // object that has no data at all
+ if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) {
+ return;
+ }
+
+ if ( !id ) {
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++;
+ } else {
+ id = internalKey;
+ }
+ }
+
+ if ( !cache[ id ] ) {
+ // Avoid exposing jQuery metadata on plain JS objects when the object
+ // is serialized using JSON.stringify
+ cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
+ }
+
+ // An object can be passed to jQuery.data instead of a key/value pair; this gets
+ // shallow copied over onto the existing cache
+ if ( typeof name === "object" || typeof name === "function" ) {
+ if ( pvt ) {
+ cache[ id ] = jQuery.extend( cache[ id ], name );
+ } else {
+ cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+ }
+ }
+
+ thisCache = cache[ id ];
+
+ // jQuery data() is stored in a separate object inside the object's internal data
+ // cache in order to avoid key collisions between internal data and user-defined
+ // data.
+ if ( !pvt ) {
+ if ( !thisCache.data ) {
+ thisCache.data = {};
+ }
+
+ thisCache = thisCache.data;
+ }
+
+ if ( data !== undefined ) {
+ thisCache[ jQuery.camelCase( name ) ] = data;
+ }
+
+ // Check for both converted-to-camel and non-converted data property names
+ // If a data property was specified
+ if ( typeof name === "string" ) {
+
+ // First Try to find as-is property data
+ ret = thisCache[ name ];
+
+ // Test for null|undefined property data
+ if ( ret == null ) {
+
+ // Try to find the camelCased property
+ ret = thisCache[ jQuery.camelCase( name ) ];
+ }
+ } else {
+ ret = thisCache;
+ }
+
+ return ret;
+}
+
+function internalRemoveData( elem, name, pvt ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, i,
+ isNode = elem.nodeType,
+
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
+ id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
+
+ // If there is already no cache entry for this object, there is no
+ // purpose in continuing
+ if ( !cache[ id ] ) {
+ return;
+ }
+
+ if ( name ) {
+
+ thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+ if ( thisCache ) {
+
+ // Support array or space separated string names for data keys
+ if ( !jQuery.isArray( name ) ) {
+
+ // try the string as a key before any manipulation
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+
+ // split the camel cased version by spaces unless a key with the spaces exists
+ name = jQuery.camelCase( name );
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+ name = name.split(" ");
+ }
+ }
+ } else {
+ // If "name" is an array of keys...
+ // When data is initially created, via ("key", "val") signature,
+ // keys will be converted to camelCase.
+ // Since there is no way to tell _how_ a key was added, remove
+ // both plain key and camelCase key. #12786
+ // This will only penalize the array argument path.
+ name = name.concat( jQuery.map( name, jQuery.camelCase ) );
+ }
+
+ i = name.length;
+ while ( i-- ) {
+ delete thisCache[ name[i] ];
+ }
+
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) {
+ return;
+ }
+ }
+ }
+
+ // See jQuery.data for more information
+ if ( !pvt ) {
+ delete cache[ id ].data;
+
+ // Don't destroy the parent cache unless the internal data object
+ // had been the only thing left in it
+ if ( !isEmptyDataObject( cache[ id ] ) ) {
+ return;
+ }
+ }
+
+ // Destroy the cache
+ if ( isNode ) {
+ jQuery.cleanData( [ elem ], true );
+
+ // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
+ /* jshint eqeqeq: false */
+ } else if ( support.deleteExpando || cache != cache.window ) {
+ /* jshint eqeqeq: true */
+ delete cache[ id ];
+
+ // When all else fails, null
+ } else {
+ cache[ id ] = null;
+ }
+}
+
+jQuery.extend({
+ cache: {},
+
+ // The following elements (space-suffixed to avoid Object.prototype collisions)
+ // throw uncatchable exceptions if you attempt to set expando properties
+ noData: {
+ "applet ": true,
+ "embed ": true,
+ // ...but Flash objects (which have this classid) *can* handle expandos
+ "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
+ },
+
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data ) {
+ return internalData( elem, name, data );
+ },
+
+ removeData: function( elem, name ) {
+ return internalRemoveData( elem, name );
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return internalData( elem, name, data, true );
+ },
+
+ _removeData: function( elem, name ) {
+ return internalRemoveData( elem, name, true );
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var i, name, data,
+ elem = this[0],
+ attrs = elem && elem.attributes;
+
+ // Special expections of .data basically thwart jQuery.access,
+ // so implement the relevant behavior ourselves
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = jQuery.data( elem );
+
+ if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
+ i = attrs.length;
+ while ( i-- ) {
+
+ // Support: IE11+
+ // The attrs elements can be null (#14894)
+ if ( attrs[ i ] ) {
+ name = attrs[ i ].name;
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = jQuery.camelCase( name.slice(5) );
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ }
+ jQuery._data( elem, "parsedAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ return arguments.length > 1 ?
+
+ // Sets one value
+ this.each(function() {
+ jQuery.data( this, key, value );
+ }) :
+
+ // Gets one value
+ // Try to fetch any internally stored data first
+ elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined;
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
+
+
+jQuery.extend({
+ queue: function( elem, type, data ) {
+ var queue;
+
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = jQuery._data( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !queue || jQuery.isArray(data) ) {
+ queue = jQuery._data( elem, type, jQuery.makeArray(data) );
+ } else {
+ queue.push( data );
+ }
+ }
+ return queue || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ startLength = queue.length,
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ startLength--;
+ }
+
+ if ( fn ) {
+
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ // clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
+
+ if ( !startLength && hooks ) {
+ hooks.empty.fire();
+ }
+ },
+
+ // not intended for public consumption - generates a queueHooks object, or returns the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return jQuery._data( elem, key ) || jQuery._data( elem, key, {
+ empty: jQuery.Callbacks("once memory").add(function() {
+ jQuery._removeData( elem, type + "queue" );
+ jQuery._removeData( elem, key );
+ })
+ });
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[0], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ // ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, obj ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
+
+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
+
+ while ( i-- ) {
+ tmp = jQuery._data( elements[ i ], type + "queueHooks" );
+ if ( tmp && tmp.empty ) {
+ count++;
+ tmp.empty.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( obj );
+ }
+});
+var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source;
+
+var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
+
+var isHidden = function( elem, el ) {
+ // isHidden might be called from jQuery#filter function;
+ // in that case, element will be second argument
+ elem = el || elem;
+ return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
+ };
+
+
+
+// Multifunctional method to get and set values of a collection
+// The value/s can optionally be executed if it's a function
+var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
+ var i = 0,
+ length = elems.length,
+ bulk = key == null;
+
+ // Sets many values
+ if ( jQuery.type( key ) === "object" ) {
+ chainable = true;
+ for ( i in key ) {
+ jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
+ }
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ chainable = true;
+
+ if ( !jQuery.isFunction( value ) ) {
+ raw = true;
+ }
+
+ if ( bulk ) {
+ // Bulk operations run against the entire set
+ if ( raw ) {
+ fn.call( elems, value );
+ fn = null;
+
+ // ...except when executing function values
+ } else {
+ bulk = fn;
+ fn = function( elem, key, value ) {
+ return bulk.call( jQuery( elem ), value );
+ };
+ }
+ }
+
+ if ( fn ) {
+ for ( ; i < length; i++ ) {
+ fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
+ }
+ }
+ }
+
+ return chainable ?
+ elems :
+
+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ length ? fn( elems[0], key ) : emptyGet;
+};
+var rcheckableType = (/^(?:checkbox|radio)$/i);
+
+
+
+(function() {
+ // Minified: var a,b,c
+ var input = document.createElement( "input" ),
+ div = document.createElement( "div" ),
+ fragment = document.createDocumentFragment();
+
+ // Setup
+ div.innerHTML = " a ";
+
+ // IE strips leading whitespace when .innerHTML is used
+ support.leadingWhitespace = div.firstChild.nodeType === 3;
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ support.tbody = !div.getElementsByTagName( "tbody" ).length;
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ support.htmlSerialize = !!div.getElementsByTagName( "link" ).length;
+
+ // Makes sure cloning an html5 element does not cause problems
+ // Where outerHTML is undefined, this still works
+ support.html5Clone =
+ document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav>";
+
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM (IE6/7)
+ input.type = "checkbox";
+ input.checked = true;
+ fragment.appendChild( input );
+ support.appendChecked = input.checked;
+
+ // Make sure textarea (and checkbox) defaultValue is properly cloned
+ // Support: IE6-IE11+
+ div.innerHTML = "";
+ support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
+
+ // #11217 - WebKit loses check when the name is after the checked attribute
+ fragment.appendChild( div );
+ div.innerHTML = " ";
+
+ // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3
+ // old WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Support: IE<9
+ // Opera does not clone events (and typeof div.attachEvent === undefined).
+ // IE9-10 clones events bound via attachEvent, but they don't trigger with .click()
+ support.noCloneEvent = true;
+ if ( div.attachEvent ) {
+ div.attachEvent( "onclick", function() {
+ support.noCloneEvent = false;
+ });
+
+ div.cloneNode( true ).click();
+ }
+
+ // Execute the test only if not already executed in another module.
+ if (support.deleteExpando == null) {
+ // Support: IE<9
+ support.deleteExpando = true;
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
+ }
+ }
+})();
+
+
+(function() {
+ var i, eventName,
+ div = document.createElement( "div" );
+
+ // Support: IE<9 (lack submit/change bubble), Firefox 23+ (lack focusin event)
+ for ( i in { submit: true, change: true, focusin: true }) {
+ eventName = "on" + i;
+
+ if ( !(support[ i + "Bubbles" ] = eventName in window) ) {
+ // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)
+ div.setAttribute( eventName, "t" );
+ support[ i + "Bubbles" ] = div.attributes[ eventName ].expando === false;
+ }
+ }
+
+ // Null elements to avoid leaks in IE.
+ div = null;
+})();
+
+
+var rformElems = /^(?:input|select|textarea)$/i,
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
+
+function returnTrue() {
+ return true;
+}
+
+function returnFalse() {
+ return false;
+}
+
+function safeActiveElement() {
+ try {
+ return document.activeElement;
+ } catch ( err ) { }
+}
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ global: {},
+
+ add: function( elem, types, handler, data, selector ) {
+ var tmp, events, t, handleObjIn,
+ special, eventHandle, handleObj,
+ handlers, type, namespaces, origType,
+ elemData = jQuery._data( elem );
+
+ // Don't attach events to noData or text/comment nodes (but allow plain objects)
+ if ( !elemData ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ if ( !(events = elemData.events) ) {
+ events = elemData.events = {};
+ }
+ if ( !(eventHandle = elemData.handle) ) {
+ eventHandle = elemData.handle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== strundefined && (!e || jQuery.event.triggered !== e.type) ?
+ jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+ eventHandle.elem = elem;
+ }
+
+ // Handle multiple events separated by a space
+ types = ( types || "" ).match( rnotwhite ) || [ "" ];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tmp[1];
+ namespaces = ( tmp[2] || "" ).split( "." ).sort();
+
+ // There *must* be a type, no attaching namespace-only handlers
+ if ( !type ) {
+ continue;
+ }
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: origType,
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+ namespace: namespaces.join(".")
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ if ( !(handlers = events[ type ]) ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener/attachEvent if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+ var j, handleObj, tmp,
+ origCount, t, events,
+ special, handlers, type,
+ namespaces, origType,
+ elemData = jQuery.hasData( elem ) && jQuery._data( elem );
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = ( types || "" ).match( rnotwhite ) || [ "" ];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tmp[1];
+ namespaces = ( tmp[2] || "" ).split( "." ).sort();
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+ handlers = events[ type ] || [];
+ tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
+
+ // Remove matching events
+ origCount = j = handlers.length;
+ while ( j-- ) {
+ handleObj = handlers[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !tmp || tmp.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ handlers.splice( j, 1 );
+
+ if ( handleObj.selector ) {
+ handlers.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( origCount && !handlers.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ delete elemData.handle;
+
+ // removeData also checks for emptiness and clears the expando if empty
+ // so use it instead of delete
+ jQuery._removeData( elem, "events" );
+ }
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+ var handle, ontype, cur,
+ bubbleType, special, tmp, i,
+ eventPath = [ elem || document ],
+ type = hasOwn.call( event, "type" ) ? event.type : event,
+ namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
+
+ cur = tmp = elem = elem || document;
+
+ // Don't do events on text and comment nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf(".") >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+ ontype = type.indexOf(":") < 0 && "on" + type;
+
+ // Caller can pass in a jQuery.Event object, Object, or just an event type string
+ event = event[ jQuery.expando ] ?
+ event :
+ new jQuery.Event( type, typeof event === "object" && event );
+
+ // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
+ event.isTrigger = onlyHandlers ? 2 : 3;
+ event.namespace = namespaces.join(".");
+ event.namespace_re = event.namespace ?
+ new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
+ null;
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data == null ?
+ [ event ] :
+ jQuery.makeArray( data, [ event ] );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ if ( !rfocusMorph.test( bubbleType + type ) ) {
+ cur = cur.parentNode;
+ }
+ for ( ; cur; cur = cur.parentNode ) {
+ eventPath.push( cur );
+ tmp = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( tmp === (elem.ownerDocument || document) ) {
+ eventPath.push( tmp.defaultView || tmp.parentWindow || window );
+ }
+ }
+
+ // Fire handlers on the event path
+ i = 0;
+ while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
+
+ event.type = i > 1 ?
+ bubbleType :
+ special.bindType || type;
+
+ // jQuery handler
+ handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+
+ // Native handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && handle.apply && jQuery.acceptData( cur ) ) {
+ event.result = handle.apply( cur, data );
+ if ( event.result === false ) {
+ event.preventDefault();
+ }
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
+ jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Can't use an .isFunction() check here because IE6/7 fails that test.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ tmp = elem[ ontype ];
+
+ if ( tmp ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ try {
+ elem[ type ]();
+ } catch ( e ) {
+ // IE<9 dies on focus/blur to hidden element (#1486,#12518)
+ // only reproducible on winXP IE8 native, not IE9 in IE8 mode
+ }
+ jQuery.event.triggered = undefined;
+
+ if ( tmp ) {
+ elem[ ontype ] = tmp;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event );
+
+ var i, ret, handleObj, matched, j,
+ handlerQueue = [],
+ args = slice.call( arguments ),
+ handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
+ special = jQuery.event.special[ event.type ] || {};
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers
+ handlerQueue = jQuery.event.handlers.call( this, event, handlers );
+
+ // Run delegates first; they may want to stop propagation beneath us
+ i = 0;
+ while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
+ event.currentTarget = matched.elem;
+
+ j = 0;
+ while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
+
+ // Triggered event must either 1) have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+ if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.handleObj = handleObj;
+ event.data = handleObj.data;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ if ( (event.result = ret) === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ handlers: function( event, handlers ) {
+ var sel, handleObj, matches, i,
+ handlerQueue = [],
+ delegateCount = handlers.delegateCount,
+ cur = event.target;
+
+ // Find delegate handlers
+ // Black-hole SVG instance trees (#13180)
+ // Avoid non-left-click bubbling in Firefox (#3861)
+ if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {
+
+ /* jshint eqeqeq: false */
+ for ( ; cur != this; cur = cur.parentNode || this ) {
+ /* jshint eqeqeq: true */
+
+ // Don't check non-elements (#13208)
+ // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
+ if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) {
+ matches = [];
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+
+ // Don't conflict with Object.prototype properties (#13203)
+ sel = handleObj.selector + " ";
+
+ if ( matches[ sel ] === undefined ) {
+ matches[ sel ] = handleObj.needsContext ?
+ jQuery( sel, this ).index( cur ) >= 0 :
+ jQuery.find( sel, this, null, [ cur ] ).length;
+ }
+ if ( matches[ sel ] ) {
+ matches.push( handleObj );
+ }
+ }
+ if ( matches.length ) {
+ handlerQueue.push({ elem: cur, handlers: matches });
+ }
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ if ( delegateCount < handlers.length ) {
+ handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
+ }
+
+ return handlerQueue;
+ },
+
+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
+
+ // Create a writable copy of the event object and normalize some properties
+ var i, prop, copy,
+ type = event.type,
+ originalEvent = event,
+ fixHook = this.fixHooks[ type ];
+
+ if ( !fixHook ) {
+ this.fixHooks[ type ] = fixHook =
+ rmouseEvent.test( type ) ? this.mouseHooks :
+ rkeyEvent.test( type ) ? this.keyHooks :
+ {};
+ }
+ copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+ event = new jQuery.Event( originalEvent );
+
+ i = copy.length;
+ while ( i-- ) {
+ prop = copy[ i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Support: IE<9
+ // Fix target property (#1925)
+ if ( !event.target ) {
+ event.target = originalEvent.srcElement || document;
+ }
+
+ // Support: Chrome 23+, Safari?
+ // Target should not be a text node (#504, #13143)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ // Support: IE<9
+ // For mouse/key events, metaKey==false if it's undefined (#3368, #11328)
+ event.metaKey = !!event.metaKey;
+
+ return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
+ },
+
+ // Includes some event props shared by KeyEvent and MouseEvent
+ props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+ fixHooks: {},
+
+ keyHooks: {
+ props: "char charCode key keyCode".split(" "),
+ filter: function( event, original ) {
+
+ // Add which for key events
+ if ( event.which == null ) {
+ event.which = original.charCode != null ? original.charCode : original.keyCode;
+ }
+
+ return event;
+ }
+ },
+
+ mouseHooks: {
+ props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+ filter: function( event, original ) {
+ var body, eventDoc, doc,
+ button = original.button,
+ fromElement = original.fromElement;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && original.clientX != null ) {
+ eventDoc = event.target.ownerDocument || document;
+ doc = eventDoc.documentElement;
+ body = eventDoc.body;
+
+ event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+ event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
+ }
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && fromElement ) {
+ event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && button !== undefined ) {
+ event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+ }
+
+ return event;
+ }
+ },
+
+ special: {
+ load: {
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+ focus: {
+ // Fire native event if possible so blur/focus sequence is correct
+ trigger: function() {
+ if ( this !== safeActiveElement() && this.focus ) {
+ try {
+ this.focus();
+ return false;
+ } catch ( e ) {
+ // Support: IE<9
+ // If we error on focus to hidden element (#1486, #12518),
+ // let .trigger() run the handlers
+ }
+ }
+ },
+ delegateType: "focusin"
+ },
+ blur: {
+ trigger: function() {
+ if ( this === safeActiveElement() && this.blur ) {
+ this.blur();
+ return false;
+ }
+ },
+ delegateType: "focusout"
+ },
+ click: {
+ // For checkbox, fire native event so checked state will be right
+ trigger: function() {
+ if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) {
+ this.click();
+ return false;
+ }
+ },
+
+ // For cross-browser consistency, don't fire native .click() on links
+ _default: function( event ) {
+ return jQuery.nodeName( event.target, "a" );
+ }
+ },
+
+ beforeunload: {
+ postDispatch: function( event ) {
+
+ // Support: Firefox 20+
+ // Firefox doesn't alert if the returnValue field is not set.
+ if ( event.result !== undefined && event.originalEvent ) {
+ event.originalEvent.returnValue = event.result;
+ }
+ }
+ }
+ },
+
+ simulate: function( type, elem, event, bubble ) {
+ // Piggyback on a donor event to simulate a different one.
+ // Fake originalEvent to avoid donor's stopPropagation, but if the
+ // simulated event prevents default then we do the same on the donor.
+ var e = jQuery.extend(
+ new jQuery.Event(),
+ event,
+ {
+ type: type,
+ isSimulated: true,
+ originalEvent: {}
+ }
+ );
+ if ( bubble ) {
+ jQuery.event.trigger( e, null, elem );
+ } else {
+ jQuery.event.dispatch.call( elem, e );
+ }
+ if ( e.isDefaultPrevented() ) {
+ event.preventDefault();
+ }
+ }
+};
+
+jQuery.removeEvent = document.removeEventListener ?
+ function( elem, type, handle ) {
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle, false );
+ }
+ } :
+ function( elem, type, handle ) {
+ var name = "on" + type;
+
+ if ( elem.detachEvent ) {
+
+ // #8545, #7054, preventing memory leaks for custom events in IE6-8
+ // detachEvent needed property on element, by name of that event, to properly expose it to GC
+ if ( typeof elem[ name ] === strundefined ) {
+ elem[ name ] = null;
+ }
+
+ elem.detachEvent( name, handle );
+ }
+ };
+
+jQuery.Event = function( src, props ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !(this instanceof jQuery.Event) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = src.defaultPrevented ||
+ src.defaultPrevented === undefined &&
+ // Support: IE < 9, Android < 4.0
+ src.returnValue === false ?
+ returnTrue :
+ returnFalse;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || jQuery.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse,
+
+ preventDefault: function() {
+ var e = this.originalEvent;
+
+ this.isDefaultPrevented = returnTrue;
+ if ( !e ) {
+ return;
+ }
+
+ // If preventDefault exists, run it on the original event
+ if ( e.preventDefault ) {
+ e.preventDefault();
+
+ // Support: IE
+ // Otherwise set the returnValue property of the original event to false
+ } else {
+ e.returnValue = false;
+ }
+ },
+ stopPropagation: function() {
+ var e = this.originalEvent;
+
+ this.isPropagationStopped = returnTrue;
+ if ( !e ) {
+ return;
+ }
+ // If stopPropagation exists, run it on the original event
+ if ( e.stopPropagation ) {
+ e.stopPropagation();
+ }
+
+ // Support: IE
+ // Set the cancelBubble property of the original event to true
+ e.cancelBubble = true;
+ },
+ stopImmediatePropagation: function() {
+ var e = this.originalEvent;
+
+ this.isImmediatePropagationStopped = returnTrue;
+
+ if ( e && e.stopImmediatePropagation ) {
+ e.stopImmediatePropagation();
+ }
+
+ this.stopPropagation();
+ }
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout",
+ pointerenter: "pointerover",
+ pointerleave: "pointerout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
+
+ handle: function( event ) {
+ var ret,
+ target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj;
+
+ // For mousenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
+ };
+});
+
+// IE submit delegation
+if ( !support.submitBubbles ) {
+
+ jQuery.event.special.submit = {
+ setup: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Lazy-add a submit handler when a descendant form may potentially be submitted
+ jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
+ // Node name check avoids a VML-related crash in IE (#9807)
+ var elem = e.target,
+ form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
+ if ( form && !jQuery._data( form, "submitBubbles" ) ) {
+ jQuery.event.add( form, "submit._submit", function( event ) {
+ event._submit_bubble = true;
+ });
+ jQuery._data( form, "submitBubbles", true );
+ }
+ });
+ // return undefined since we don't need an event listener
+ },
+
+ postDispatch: function( event ) {
+ // If form was submitted by the user, bubble the event up the tree
+ if ( event._submit_bubble ) {
+ delete event._submit_bubble;
+ if ( this.parentNode && !event.isTrigger ) {
+ jQuery.event.simulate( "submit", this.parentNode, event, true );
+ }
+ }
+ },
+
+ teardown: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+ jQuery.event.remove( this, "._submit" );
+ }
+ };
+}
+
+// IE change delegation and checkbox/radio fix
+if ( !support.changeBubbles ) {
+
+ jQuery.event.special.change = {
+
+ setup: function() {
+
+ if ( rformElems.test( this.nodeName ) ) {
+ // IE doesn't fire change on a check/radio until blur; trigger it on click
+ // after a propertychange. Eat the blur-change in special.change.handle.
+ // This still fires onchange a second time for check/radio after blur.
+ if ( this.type === "checkbox" || this.type === "radio" ) {
+ jQuery.event.add( this, "propertychange._change", function( event ) {
+ if ( event.originalEvent.propertyName === "checked" ) {
+ this._just_changed = true;
+ }
+ });
+ jQuery.event.add( this, "click._change", function( event ) {
+ if ( this._just_changed && !event.isTrigger ) {
+ this._just_changed = false;
+ }
+ // Allow triggered, simulated change events (#11500)
+ jQuery.event.simulate( "change", this, event, true );
+ });
+ }
+ return false;
+ }
+ // Delegated event; lazy-add a change handler on descendant inputs
+ jQuery.event.add( this, "beforeactivate._change", function( e ) {
+ var elem = e.target;
+
+ if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) {
+ jQuery.event.add( elem, "change._change", function( event ) {
+ if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
+ jQuery.event.simulate( "change", this.parentNode, event, true );
+ }
+ });
+ jQuery._data( elem, "changeBubbles", true );
+ }
+ });
+ },
+
+ handle: function( event ) {
+ var elem = event.target;
+
+ // Swallow native change events from checkbox/radio, we already triggered them above
+ if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
+ return event.handleObj.handler.apply( this, arguments );
+ }
+ },
+
+ teardown: function() {
+ jQuery.event.remove( this, "._change" );
+
+ return !rformElems.test( this.nodeName );
+ }
+ };
+}
+
+// Create "bubbling" focus and blur events
+if ( !support.focusinBubbles ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // Attach a single capturing handler on the document while someone wants focusin/focusout
+ var handler = function( event ) {
+ jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+ };
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ var doc = this.ownerDocument || this,
+ attaches = jQuery._data( doc, fix );
+
+ if ( !attaches ) {
+ doc.addEventListener( orig, handler, true );
+ }
+ jQuery._data( doc, fix, ( attaches || 0 ) + 1 );
+ },
+ teardown: function() {
+ var doc = this.ownerDocument || this,
+ attaches = jQuery._data( doc, fix ) - 1;
+
+ if ( !attaches ) {
+ doc.removeEventListener( orig, handler, true );
+ jQuery._removeData( doc, fix );
+ } else {
+ jQuery._data( doc, fix, attaches );
+ }
+ }
+ };
+ });
+}
+
+jQuery.fn.extend({
+
+ on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+ var type, origFn;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) {
+ // ( types-Object, data )
+ data = data || selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ this.on( type, selector, data, types[ type ], one );
+ }
+ return this;
+ }
+
+ if ( data == null && fn == null ) {
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return this;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return this.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ });
+ },
+ one: function( types, selector, data, fn ) {
+ return this.on( types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ var handleObj, type;
+ if ( types && types.preventDefault && types.handleObj ) {
+ // ( event ) dispatched jQuery.Event
+ handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+ // ( types-object [, selector] )
+ for ( type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each(function() {
+ jQuery.event.remove( this, types, fn, selector );
+ });
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+ triggerHandler: function( type, data ) {
+ var elem = this[0];
+ if ( elem ) {
+ return jQuery.event.trigger( type, data, elem, true );
+ }
+ }
+});
+
+
+function createSafeFragment( document ) {
+ var list = nodeNames.split( "|" ),
+ safeFrag = document.createDocumentFragment();
+
+ if ( safeFrag.createElement ) {
+ while ( list.length ) {
+ safeFrag.createElement(
+ list.pop()
+ );
+ }
+ }
+ return safeFrag;
+}
+
+var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
+ "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
+ rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
+ rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
+ rleadingWhitespace = /^\s+/,
+ rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
+ rtagName = /<([\w:]+)/,
+ rtbody = /\s*$/g,
+
+ // We have to close these tags to support XHTML (#13200)
+ wrapMap = {
+ option: [ 1, "", " " ],
+ legend: [ 1, "", " " ],
+ area: [ 1, "", " " ],
+ param: [ 1, "", " " ],
+ thead: [ 1, "" ],
+ tr: [ 2, "" ],
+ col: [ 2, "" ],
+ td: [ 3, "" ],
+
+ // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
+ // unless wrapped in a div with non-breaking characters in front of it.
+ _default: support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X", "
" ]
+ },
+ safeFragment = createSafeFragment( document ),
+ fragmentDiv = safeFragment.appendChild( document.createElement("div") );
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+function getAll( context, tag ) {
+ var elems, elem,
+ i = 0,
+ found = typeof context.getElementsByTagName !== strundefined ? context.getElementsByTagName( tag || "*" ) :
+ typeof context.querySelectorAll !== strundefined ? context.querySelectorAll( tag || "*" ) :
+ undefined;
+
+ if ( !found ) {
+ for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) {
+ if ( !tag || jQuery.nodeName( elem, tag ) ) {
+ found.push( elem );
+ } else {
+ jQuery.merge( found, getAll( elem, tag ) );
+ }
+ }
+ }
+
+ return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
+ jQuery.merge( [ context ], found ) :
+ found;
+}
+
+// Used in buildFragment, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+ if ( rcheckableType.test( elem.type ) ) {
+ elem.defaultChecked = elem.checked;
+ }
+}
+
+// Support: IE<8
+// Manipulating tables requires a tbody
+function manipulationTarget( elem, content ) {
+ return jQuery.nodeName( elem, "table" ) &&
+ jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ?
+
+ elem.getElementsByTagName("tbody")[0] ||
+ elem.appendChild( elem.ownerDocument.createElement("tbody") ) :
+ elem;
+}
+
+// Replace/restore the type attribute of script elements for safe DOM manipulation
+function disableScript( elem ) {
+ elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type;
+ return elem;
+}
+function restoreScript( elem ) {
+ var match = rscriptTypeMasked.exec( elem.type );
+ if ( match ) {
+ elem.type = match[1];
+ } else {
+ elem.removeAttribute("type");
+ }
+ return elem;
+}
+
+// Mark scripts as having already been evaluated
+function setGlobalEval( elems, refElements ) {
+ var elem,
+ i = 0;
+ for ( ; (elem = elems[i]) != null; i++ ) {
+ jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) );
+ }
+}
+
+function cloneCopyEvent( src, dest ) {
+
+ if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+ return;
+ }
+
+ var type, i, l,
+ oldData = jQuery._data( src ),
+ curData = jQuery._data( dest, oldData ),
+ events = oldData.events;
+
+ if ( events ) {
+ delete curData.handle;
+ curData.events = {};
+
+ for ( type in events ) {
+ for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+ jQuery.event.add( dest, type, events[ type ][ i ] );
+ }
+ }
+ }
+
+ // make the cloned public data object a copy from the original
+ if ( curData.data ) {
+ curData.data = jQuery.extend( {}, curData.data );
+ }
+}
+
+function fixCloneNodeIssues( src, dest ) {
+ var nodeName, e, data;
+
+ // We do not need to do anything for non-Elements
+ if ( dest.nodeType !== 1 ) {
+ return;
+ }
+
+ nodeName = dest.nodeName.toLowerCase();
+
+ // IE6-8 copies events bound via attachEvent when using cloneNode.
+ if ( !support.noCloneEvent && dest[ jQuery.expando ] ) {
+ data = jQuery._data( dest );
+
+ for ( e in data.events ) {
+ jQuery.removeEvent( dest, e, data.handle );
+ }
+
+ // Event data gets referenced instead of copied if the expando gets copied too
+ dest.removeAttribute( jQuery.expando );
+ }
+
+ // IE blanks contents when cloning scripts, and tries to evaluate newly-set text
+ if ( nodeName === "script" && dest.text !== src.text ) {
+ disableScript( dest ).text = src.text;
+ restoreScript( dest );
+
+ // IE6-10 improperly clones children of object elements using classid.
+ // IE10 throws NoModificationAllowedError if parent is null, #12132.
+ } else if ( nodeName === "object" ) {
+ if ( dest.parentNode ) {
+ dest.outerHTML = src.outerHTML;
+ }
+
+ // This path appears unavoidable for IE9. When cloning an object
+ // element in IE9, the outerHTML strategy above is not sufficient.
+ // If the src has innerHTML and the destination does not,
+ // copy the src.innerHTML into the dest.innerHTML. #10324
+ if ( support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) {
+ dest.innerHTML = src.innerHTML;
+ }
+
+ } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
+ // IE6-8 fails to persist the checked state of a cloned checkbox
+ // or radio button. Worse, IE6-7 fail to give the cloned element
+ // a checked appearance if the defaultChecked value isn't also set
+
+ dest.defaultChecked = dest.checked = src.checked;
+
+ // IE6-7 get confused and end up setting the value of a cloned
+ // checkbox/radio button to an empty string instead of "on"
+ if ( dest.value !== src.value ) {
+ dest.value = src.value;
+ }
+
+ // IE6-8 fails to return the selected option to the default selected
+ // state when cloning options
+ } else if ( nodeName === "option" ) {
+ dest.defaultSelected = dest.selected = src.defaultSelected;
+
+ // IE6-8 fails to set the defaultValue to the correct value when
+ // cloning other types of input fields
+ } else if ( nodeName === "input" || nodeName === "textarea" ) {
+ dest.defaultValue = src.defaultValue;
+ }
+}
+
+jQuery.extend({
+ clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+ var destElements, node, clone, i, srcElements,
+ inPage = jQuery.contains( elem.ownerDocument, elem );
+
+ if ( support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
+ clone = elem.cloneNode( true );
+
+ // IE<=8 does not properly clone detached, unknown element nodes
+ } else {
+ fragmentDiv.innerHTML = elem.outerHTML;
+ fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
+ }
+
+ if ( (!support.noCloneEvent || !support.noCloneChecked) &&
+ (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+
+ // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
+ destElements = getAll( clone );
+ srcElements = getAll( elem );
+
+ // Fix all IE cloning issues
+ for ( i = 0; (node = srcElements[i]) != null; ++i ) {
+ // Ensure that the destination node is not null; Fixes #9587
+ if ( destElements[i] ) {
+ fixCloneNodeIssues( node, destElements[i] );
+ }
+ }
+ }
+
+ // Copy the events from the original to the clone
+ if ( dataAndEvents ) {
+ if ( deepDataAndEvents ) {
+ srcElements = srcElements || getAll( elem );
+ destElements = destElements || getAll( clone );
+
+ for ( i = 0; (node = srcElements[i]) != null; i++ ) {
+ cloneCopyEvent( node, destElements[i] );
+ }
+ } else {
+ cloneCopyEvent( elem, clone );
+ }
+ }
+
+ // Preserve script evaluation history
+ destElements = getAll( clone, "script" );
+ if ( destElements.length > 0 ) {
+ setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
+ }
+
+ destElements = srcElements = node = null;
+
+ // Return the cloned set
+ return clone;
+ },
+
+ buildFragment: function( elems, context, scripts, selection ) {
+ var j, elem, contains,
+ tmp, tag, tbody, wrap,
+ l = elems.length,
+
+ // Ensure a safe fragment
+ safe = createSafeFragment( context ),
+
+ nodes = [],
+ i = 0;
+
+ for ( ; i < l; i++ ) {
+ elem = elems[ i ];
+
+ if ( elem || elem === 0 ) {
+
+ // Add nodes directly
+ if ( jQuery.type( elem ) === "object" ) {
+ jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
+
+ // Convert non-html into a text node
+ } else if ( !rhtml.test( elem ) ) {
+ nodes.push( context.createTextNode( elem ) );
+
+ // Convert html into DOM nodes
+ } else {
+ tmp = tmp || safe.appendChild( context.createElement("div") );
+
+ // Deserialize a standard representation
+ tag = (rtagName.exec( elem ) || [ "", "" ])[ 1 ].toLowerCase();
+ wrap = wrapMap[ tag ] || wrapMap._default;
+
+ tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>$2>" ) + wrap[2];
+
+ // Descend through wrappers to the right content
+ j = wrap[0];
+ while ( j-- ) {
+ tmp = tmp.lastChild;
+ }
+
+ // Manually add leading whitespace removed by IE
+ if ( !support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+ nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) );
+ }
+
+ // Remove IE's autoinserted from table fragments
+ if ( !support.tbody ) {
+
+ // String was a , *may* have spurious
+ elem = tag === "table" && !rtbody.test( elem ) ?
+ tmp.firstChild :
+
+ // String was a bare or
+ wrap[1] === "" && !rtbody.test( elem ) ?
+ tmp :
+ 0;
+
+ j = elem && elem.childNodes.length;
+ while ( j-- ) {
+ if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) {
+ elem.removeChild( tbody );
+ }
+ }
+ }
+
+ jQuery.merge( nodes, tmp.childNodes );
+
+ // Fix #12392 for WebKit and IE > 9
+ tmp.textContent = "";
+
+ // Fix #12392 for oldIE
+ while ( tmp.firstChild ) {
+ tmp.removeChild( tmp.firstChild );
+ }
+
+ // Remember the top-level container for proper cleanup
+ tmp = safe.lastChild;
+ }
+ }
+ }
+
+ // Fix #11356: Clear elements from fragment
+ if ( tmp ) {
+ safe.removeChild( tmp );
+ }
+
+ // Reset defaultChecked for any radios and checkboxes
+ // about to be appended to the DOM in IE 6/7 (#8060)
+ if ( !support.appendChecked ) {
+ jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked );
+ }
+
+ i = 0;
+ while ( (elem = nodes[ i++ ]) ) {
+
+ // #4087 - If origin and destination elements are the same, and this is
+ // that element, do not do anything
+ if ( selection && jQuery.inArray( elem, selection ) !== -1 ) {
+ continue;
+ }
+
+ contains = jQuery.contains( elem.ownerDocument, elem );
+
+ // Append to fragment
+ tmp = getAll( safe.appendChild( elem ), "script" );
+
+ // Preserve script evaluation history
+ if ( contains ) {
+ setGlobalEval( tmp );
+ }
+
+ // Capture executables
+ if ( scripts ) {
+ j = 0;
+ while ( (elem = tmp[ j++ ]) ) {
+ if ( rscriptType.test( elem.type || "" ) ) {
+ scripts.push( elem );
+ }
+ }
+ }
+ }
+
+ tmp = null;
+
+ return safe;
+ },
+
+ cleanData: function( elems, /* internal */ acceptData ) {
+ var elem, type, id, data,
+ i = 0,
+ internalKey = jQuery.expando,
+ cache = jQuery.cache,
+ deleteExpando = support.deleteExpando,
+ special = jQuery.event.special;
+
+ for ( ; (elem = elems[i]) != null; i++ ) {
+ if ( acceptData || jQuery.acceptData( elem ) ) {
+
+ id = elem[ internalKey ];
+ data = id && cache[ id ];
+
+ if ( data ) {
+ if ( data.events ) {
+ for ( type in data.events ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
+
+ // This is a shortcut to avoid jQuery.event.remove's overhead
+ } else {
+ jQuery.removeEvent( elem, type, data.handle );
+ }
+ }
+ }
+
+ // Remove cache only if it was not already removed by jQuery.event.remove
+ if ( cache[ id ] ) {
+
+ delete cache[ id ];
+
+ // IE does not allow us to delete expando properties from nodes,
+ // nor does it have a removeAttribute function on Document nodes;
+ // we must handle all of these cases
+ if ( deleteExpando ) {
+ delete elem[ internalKey ];
+
+ } else if ( typeof elem.removeAttribute !== strundefined ) {
+ elem.removeAttribute( internalKey );
+
+ } else {
+ elem[ internalKey ] = null;
+ }
+
+ deletedIds.push( id );
+ }
+ }
+ }
+ }
+ }
+});
+
+jQuery.fn.extend({
+ text: function( value ) {
+ return access( this, function( value ) {
+ return value === undefined ?
+ jQuery.text( this ) :
+ this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
+ }, null, value, arguments.length );
+ },
+
+ append: function() {
+ return this.domManip( arguments, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+ var target = manipulationTarget( this, elem );
+ target.appendChild( elem );
+ }
+ });
+ },
+
+ prepend: function() {
+ return this.domManip( arguments, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+ var target = manipulationTarget( this, elem );
+ target.insertBefore( elem, target.firstChild );
+ }
+ });
+ },
+
+ before: function() {
+ return this.domManip( arguments, function( elem ) {
+ if ( this.parentNode ) {
+ this.parentNode.insertBefore( elem, this );
+ }
+ });
+ },
+
+ after: function() {
+ return this.domManip( arguments, function( elem ) {
+ if ( this.parentNode ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ }
+ });
+ },
+
+ remove: function( selector, keepData /* Internal Use Only */ ) {
+ var elem,
+ elems = selector ? jQuery.filter( selector, this ) : this,
+ i = 0;
+
+ for ( ; (elem = elems[i]) != null; i++ ) {
+
+ if ( !keepData && elem.nodeType === 1 ) {
+ jQuery.cleanData( getAll( elem ) );
+ }
+
+ if ( elem.parentNode ) {
+ if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
+ setGlobalEval( getAll( elem, "script" ) );
+ }
+ elem.parentNode.removeChild( elem );
+ }
+ }
+
+ return this;
+ },
+
+ empty: function() {
+ var elem,
+ i = 0;
+
+ for ( ; (elem = this[i]) != null; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( getAll( elem, false ) );
+ }
+
+ // Remove any remaining nodes
+ while ( elem.firstChild ) {
+ elem.removeChild( elem.firstChild );
+ }
+
+ // If this is a select, ensure that it displays empty (#12336)
+ // Support: IE<9
+ if ( elem.options && jQuery.nodeName( elem, "select" ) ) {
+ elem.options.length = 0;
+ }
+ }
+
+ return this;
+ },
+
+ clone: function( dataAndEvents, deepDataAndEvents ) {
+ dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+ deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+ return this.map(function() {
+ return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+ });
+ },
+
+ html: function( value ) {
+ return access( this, function( value ) {
+ var elem = this[ 0 ] || {},
+ i = 0,
+ l = this.length;
+
+ if ( value === undefined ) {
+ return elem.nodeType === 1 ?
+ elem.innerHTML.replace( rinlinejQuery, "" ) :
+ undefined;
+ }
+
+ // See if we can take a shortcut and just use innerHTML
+ if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+ ( support.htmlSerialize || !rnoshimcache.test( value ) ) &&
+ ( support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
+ !wrapMap[ (rtagName.exec( value ) || [ "", "" ])[ 1 ].toLowerCase() ] ) {
+
+ value = value.replace( rxhtmlTag, "<$1>$2>" );
+
+ try {
+ for (; i < l; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ elem = this[i] || {};
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( getAll( elem, false ) );
+ elem.innerHTML = value;
+ }
+ }
+
+ elem = 0;
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch(e) {}
+ }
+
+ if ( elem ) {
+ this.empty().append( value );
+ }
+ }, null, value, arguments.length );
+ },
+
+ replaceWith: function() {
+ var arg = arguments[ 0 ];
+
+ // Make the changes, replacing each context element with the new content
+ this.domManip( arguments, function( elem ) {
+ arg = this.parentNode;
+
+ jQuery.cleanData( getAll( this ) );
+
+ if ( arg ) {
+ arg.replaceChild( elem, this );
+ }
+ });
+
+ // Force removal if there was no new content (e.g., from empty arguments)
+ return arg && (arg.length || arg.nodeType) ? this : this.remove();
+ },
+
+ detach: function( selector ) {
+ return this.remove( selector, true );
+ },
+
+ domManip: function( args, callback ) {
+
+ // Flatten any nested arrays
+ args = concat.apply( [], args );
+
+ var first, node, hasScripts,
+ scripts, doc, fragment,
+ i = 0,
+ l = this.length,
+ set = this,
+ iNoClone = l - 1,
+ value = args[0],
+ isFunction = jQuery.isFunction( value );
+
+ // We can't cloneNode fragments that contain checked, in WebKit
+ if ( isFunction ||
+ ( l > 1 && typeof value === "string" &&
+ !support.checkClone && rchecked.test( value ) ) ) {
+ return this.each(function( index ) {
+ var self = set.eq( index );
+ if ( isFunction ) {
+ args[0] = value.call( this, index, self.html() );
+ }
+ self.domManip( args, callback );
+ });
+ }
+
+ if ( l ) {
+ fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
+ first = fragment.firstChild;
+
+ if ( fragment.childNodes.length === 1 ) {
+ fragment = first;
+ }
+
+ if ( first ) {
+ scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
+ hasScripts = scripts.length;
+
+ // Use the original fragment for the last item instead of the first because it can end up
+ // being emptied incorrectly in certain situations (#8070).
+ for ( ; i < l; i++ ) {
+ node = fragment;
+
+ if ( i !== iNoClone ) {
+ node = jQuery.clone( node, true, true );
+
+ // Keep references to cloned scripts for later restoration
+ if ( hasScripts ) {
+ jQuery.merge( scripts, getAll( node, "script" ) );
+ }
+ }
+
+ callback.call( this[i], node, i );
+ }
+
+ if ( hasScripts ) {
+ doc = scripts[ scripts.length - 1 ].ownerDocument;
+
+ // Reenable scripts
+ jQuery.map( scripts, restoreScript );
+
+ // Evaluate executable scripts on first document insertion
+ for ( i = 0; i < hasScripts; i++ ) {
+ node = scripts[ i ];
+ if ( rscriptType.test( node.type || "" ) &&
+ !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) {
+
+ if ( node.src ) {
+ // Optional AJAX dependency, but won't run scripts if not present
+ if ( jQuery._evalUrl ) {
+ jQuery._evalUrl( node.src );
+ }
+ } else {
+ jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) );
+ }
+ }
+ }
+ }
+
+ // Fix #11809: Avoid leaking memory
+ fragment = first = null;
+ }
+ }
+
+ return this;
+ }
+});
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var elems,
+ i = 0,
+ ret = [],
+ insert = jQuery( selector ),
+ last = insert.length - 1;
+
+ for ( ; i <= last; i++ ) {
+ elems = i === last ? this : this.clone(true);
+ jQuery( insert[i] )[ original ]( elems );
+
+ // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get()
+ push.apply( ret, elems.get() );
+ }
+
+ return this.pushStack( ret );
+ };
+});
+
+
+var iframe,
+ elemdisplay = {};
+
+/**
+ * Retrieve the actual display of a element
+ * @param {String} name nodeName of the element
+ * @param {Object} doc Document object
+ */
+// Called only from within defaultDisplay
+function actualDisplay( name, doc ) {
+ var style,
+ elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),
+
+ // getDefaultComputedStyle might be reliably used only on attached element
+ display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ?
+
+ // Use of this method is a temporary fix (more like optmization) until something better comes along,
+ // since it was removed from specification and supported only in FF
+ style.display : jQuery.css( elem[ 0 ], "display" );
+
+ // We don't have any data stored on the element,
+ // so use "detach" method as fast way to get rid of the element
+ elem.detach();
+
+ return display;
+}
+
+/**
+ * Try to determine the default display value of an element
+ * @param {String} nodeName
+ */
+function defaultDisplay( nodeName ) {
+ var doc = document,
+ display = elemdisplay[ nodeName ];
+
+ if ( !display ) {
+ display = actualDisplay( nodeName, doc );
+
+ // If the simple way fails, read from inside an iframe
+ if ( display === "none" || !display ) {
+
+ // Use the already-created iframe if possible
+ iframe = (iframe || jQuery( "" )).appendTo( doc.documentElement );
+
+ // Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
+ doc = ( iframe[ 0 ].contentWindow || iframe[ 0 ].contentDocument ).document;
+
+ // Support: IE
+ doc.write();
+ doc.close();
+
+ display = actualDisplay( nodeName, doc );
+ iframe.detach();
+ }
+
+ // Store the correct default display
+ elemdisplay[ nodeName ] = display;
+ }
+
+ return display;
+}
+
+
+(function() {
+ var shrinkWrapBlocksVal;
+
+ support.shrinkWrapBlocks = function() {
+ if ( shrinkWrapBlocksVal != null ) {
+ return shrinkWrapBlocksVal;
+ }
+
+ // Will be changed later if needed.
+ shrinkWrapBlocksVal = false;
+
+ // Minified: var b,c,d
+ var div, body, container;
+
+ body = document.getElementsByTagName( "body" )[ 0 ];
+ if ( !body || !body.style ) {
+ // Test fired too early or in an unsupported environment, exit.
+ return;
+ }
+
+ // Setup
+ div = document.createElement( "div" );
+ container = document.createElement( "div" );
+ container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px";
+ body.appendChild( container ).appendChild( div );
+
+ // Support: IE6
+ // Check if elements with layout shrink-wrap their children
+ if ( typeof div.style.zoom !== strundefined ) {
+ // Reset CSS: box-sizing; display; margin; border
+ div.style.cssText =
+ // Support: Firefox<29, Android 2.3
+ // Vendor-prefix box-sizing
+ "-webkit-box-sizing:content-box;-moz-box-sizing:content-box;" +
+ "box-sizing:content-box;display:block;margin:0;border:0;" +
+ "padding:1px;width:1px;zoom:1";
+ div.appendChild( document.createElement( "div" ) ).style.width = "5px";
+ shrinkWrapBlocksVal = div.offsetWidth !== 3;
+ }
+
+ body.removeChild( container );
+
+ return shrinkWrapBlocksVal;
+ };
+
+})();
+var rmargin = (/^margin/);
+
+var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
+
+
+
+var getStyles, curCSS,
+ rposition = /^(top|right|bottom|left)$/;
+
+if ( window.getComputedStyle ) {
+ getStyles = function( elem ) {
+ // Support: IE<=11+, Firefox<=30+ (#15098, #14150)
+ // IE throws on elements created in popups
+ // FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
+ if ( elem.ownerDocument.defaultView.opener ) {
+ return elem.ownerDocument.defaultView.getComputedStyle( elem, null );
+ }
+
+ return window.getComputedStyle( elem, null );
+ };
+
+ curCSS = function( elem, name, computed ) {
+ var width, minWidth, maxWidth, ret,
+ style = elem.style;
+
+ computed = computed || getStyles( elem );
+
+ // getPropertyValue is only needed for .css('filter') in IE9, see #12537
+ ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined;
+
+ if ( computed ) {
+
+ if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
+ ret = jQuery.style( elem, name );
+ }
+
+ // A tribute to the "awesome hack by Dean Edwards"
+ // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right
+ // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
+ // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+ if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
+
+ // Remember the original values
+ width = style.width;
+ minWidth = style.minWidth;
+ maxWidth = style.maxWidth;
+
+ // Put in the new values to get a computed value out
+ style.minWidth = style.maxWidth = style.width = ret;
+ ret = computed.width;
+
+ // Revert the changed values
+ style.width = width;
+ style.minWidth = minWidth;
+ style.maxWidth = maxWidth;
+ }
+ }
+
+ // Support: IE
+ // IE returns zIndex value as an integer.
+ return ret === undefined ?
+ ret :
+ ret + "";
+ };
+} else if ( document.documentElement.currentStyle ) {
+ getStyles = function( elem ) {
+ return elem.currentStyle;
+ };
+
+ curCSS = function( elem, name, computed ) {
+ var left, rs, rsLeft, ret,
+ style = elem.style;
+
+ computed = computed || getStyles( elem );
+ ret = computed ? computed[ name ] : undefined;
+
+ // Avoid setting ret to empty string here
+ // so we don't default to auto
+ if ( ret == null && style && style[ name ] ) {
+ ret = style[ name ];
+ }
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ // but not position css attributes, as those are proportional to the parent element instead
+ // and we can't measure the parent instead because it might trigger a "stacking dolls" problem
+ if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
+
+ // Remember the original values
+ left = style.left;
+ rs = elem.runtimeStyle;
+ rsLeft = rs && rs.left;
+
+ // Put in the new values to get a computed value out
+ if ( rsLeft ) {
+ rs.left = elem.currentStyle.left;
+ }
+ style.left = name === "fontSize" ? "1em" : ret;
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ if ( rsLeft ) {
+ rs.left = rsLeft;
+ }
+ }
+
+ // Support: IE
+ // IE returns zIndex value as an integer.
+ return ret === undefined ?
+ ret :
+ ret + "" || "auto";
+ };
+}
+
+
+
+
+function addGetHookIf( conditionFn, hookFn ) {
+ // Define the hook, we'll check on the first run if it's really needed.
+ return {
+ get: function() {
+ var condition = conditionFn();
+
+ if ( condition == null ) {
+ // The test was not ready at this point; screw the hook this time
+ // but check again when needed next time.
+ return;
+ }
+
+ if ( condition ) {
+ // Hook not needed (or it's not possible to use it due to missing dependency),
+ // remove it.
+ // Since there are no other hooks for marginRight, remove the whole object.
+ delete this.get;
+ return;
+ }
+
+ // Hook needed; redefine it so that the support test is not executed again.
+
+ return (this.get = hookFn).apply( this, arguments );
+ }
+ };
+}
+
+
+(function() {
+ // Minified: var b,c,d,e,f,g, h,i
+ var div, style, a, pixelPositionVal, boxSizingReliableVal,
+ reliableHiddenOffsetsVal, reliableMarginRightVal;
+
+ // Setup
+ div = document.createElement( "div" );
+ div.innerHTML = " a ";
+ a = div.getElementsByTagName( "a" )[ 0 ];
+ style = a && a.style;
+
+ // Finish early in limited (non-browser) environments
+ if ( !style ) {
+ return;
+ }
+
+ style.cssText = "float:left;opacity:.5";
+
+ // Support: IE<9
+ // Make sure that element opacity exists (as opposed to filter)
+ support.opacity = style.opacity === "0.5";
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ support.cssFloat = !!style.cssFloat;
+
+ div.style.backgroundClip = "content-box";
+ div.cloneNode( true ).style.backgroundClip = "";
+ support.clearCloneStyle = div.style.backgroundClip === "content-box";
+
+ // Support: Firefox<29, Android 2.3
+ // Vendor-prefix box-sizing
+ support.boxSizing = style.boxSizing === "" || style.MozBoxSizing === "" ||
+ style.WebkitBoxSizing === "";
+
+ jQuery.extend(support, {
+ reliableHiddenOffsets: function() {
+ if ( reliableHiddenOffsetsVal == null ) {
+ computeStyleTests();
+ }
+ return reliableHiddenOffsetsVal;
+ },
+
+ boxSizingReliable: function() {
+ if ( boxSizingReliableVal == null ) {
+ computeStyleTests();
+ }
+ return boxSizingReliableVal;
+ },
+
+ pixelPosition: function() {
+ if ( pixelPositionVal == null ) {
+ computeStyleTests();
+ }
+ return pixelPositionVal;
+ },
+
+ // Support: Android 2.3
+ reliableMarginRight: function() {
+ if ( reliableMarginRightVal == null ) {
+ computeStyleTests();
+ }
+ return reliableMarginRightVal;
+ }
+ });
+
+ function computeStyleTests() {
+ // Minified: var b,c,d,j
+ var div, body, container, contents;
+
+ body = document.getElementsByTagName( "body" )[ 0 ];
+ if ( !body || !body.style ) {
+ // Test fired too early or in an unsupported environment, exit.
+ return;
+ }
+
+ // Setup
+ div = document.createElement( "div" );
+ container = document.createElement( "div" );
+ container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px";
+ body.appendChild( container ).appendChild( div );
+
+ div.style.cssText =
+ // Support: Firefox<29, Android 2.3
+ // Vendor-prefix box-sizing
+ "-webkit-box-sizing:border-box;-moz-box-sizing:border-box;" +
+ "box-sizing:border-box;display:block;margin-top:1%;top:1%;" +
+ "border:1px;padding:1px;width:4px;position:absolute";
+
+ // Support: IE<9
+ // Assume reasonable values in the absence of getComputedStyle
+ pixelPositionVal = boxSizingReliableVal = false;
+ reliableMarginRightVal = true;
+
+ // Check for getComputedStyle so that this code is not run in IE<9.
+ if ( window.getComputedStyle ) {
+ pixelPositionVal = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
+ boxSizingReliableVal =
+ ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
+
+ // Support: Android 2.3
+ // Div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container (#3333)
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ contents = div.appendChild( document.createElement( "div" ) );
+
+ // Reset CSS: box-sizing; display; margin; border; padding
+ contents.style.cssText = div.style.cssText =
+ // Support: Firefox<29, Android 2.3
+ // Vendor-prefix box-sizing
+ "-webkit-box-sizing:content-box;-moz-box-sizing:content-box;" +
+ "box-sizing:content-box;display:block;margin:0;border:0;padding:0";
+ contents.style.marginRight = contents.style.width = "0";
+ div.style.width = "1px";
+
+ reliableMarginRightVal =
+ !parseFloat( ( window.getComputedStyle( contents, null ) || {} ).marginRight );
+
+ div.removeChild( contents );
+ }
+
+ // Support: IE8
+ // Check if table cells still have offsetWidth/Height when they are set
+ // to display:none and there are still other visible table cells in a
+ // table row; if so, offsetWidth/Height are not reliable for use when
+ // determining if an element has been hidden directly using
+ // display:none (it is still safe to use offsets if a parent element is
+ // hidden; don safety goggles and see bug #4512 for more information).
+ div.innerHTML = "";
+ contents = div.getElementsByTagName( "td" );
+ contents[ 0 ].style.cssText = "margin:0;border:0;padding:0;display:none";
+ reliableHiddenOffsetsVal = contents[ 0 ].offsetHeight === 0;
+ if ( reliableHiddenOffsetsVal ) {
+ contents[ 0 ].style.display = "";
+ contents[ 1 ].style.display = "none";
+ reliableHiddenOffsetsVal = contents[ 0 ].offsetHeight === 0;
+ }
+
+ body.removeChild( container );
+ }
+
+})();
+
+
+// A method for quickly swapping in/out CSS properties to get correct calculations.
+jQuery.swap = function( elem, options, callback, args ) {
+ var ret, name,
+ old = {};
+
+ // Remember the old values, and insert the new ones
+ for ( name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ ret = callback.apply( elem, args || [] );
+
+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+
+ return ret;
+};
+
+
+var
+ ralpha = /alpha\([^)]*\)/i,
+ ropacity = /opacity\s*=\s*([^)]*)/,
+
+ // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
+ // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
+ rdisplayswap = /^(none|table(?!-c[ea]).+)/,
+ rnumsplit = new RegExp( "^(" + pnum + ")(.*)$", "i" ),
+ rrelNum = new RegExp( "^([+-])=(" + pnum + ")", "i" ),
+
+ cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+ cssNormalTransform = {
+ letterSpacing: "0",
+ fontWeight: "400"
+ },
+
+ cssPrefixes = [ "Webkit", "O", "Moz", "ms" ];
+
+
+// return a css property mapped to a potentially vendor prefixed property
+function vendorPropName( style, name ) {
+
+ // shortcut for names that are not vendor prefixed
+ if ( name in style ) {
+ return name;
+ }
+
+ // check for vendor prefixed names
+ var capName = name.charAt(0).toUpperCase() + name.slice(1),
+ origName = name,
+ i = cssPrefixes.length;
+
+ while ( i-- ) {
+ name = cssPrefixes[ i ] + capName;
+ if ( name in style ) {
+ return name;
+ }
+ }
+
+ return origName;
+}
+
+function showHide( elements, show ) {
+ var display, elem, hidden,
+ values = [],
+ index = 0,
+ length = elements.length;
+
+ for ( ; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+
+ values[ index ] = jQuery._data( elem, "olddisplay" );
+ display = elem.style.display;
+ if ( show ) {
+ // Reset the inline display of this element to learn if it is
+ // being hidden by cascaded rules or not
+ if ( !values[ index ] && display === "none" ) {
+ elem.style.display = "";
+ }
+
+ // Set elements which have been overridden with display: none
+ // in a stylesheet to whatever the default browser style is
+ // for such an element
+ if ( elem.style.display === "" && isHidden( elem ) ) {
+ values[ index ] = jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) );
+ }
+ } else {
+ hidden = isHidden( elem );
+
+ if ( display && display !== "none" || !hidden ) {
+ jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) );
+ }
+ }
+ }
+
+ // Set the display of most of the elements in a second loop
+ // to avoid the constant reflow
+ for ( index = 0; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+ if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
+ elem.style.display = show ? values[ index ] || "" : "none";
+ }
+ }
+
+ return elements;
+}
+
+function setPositiveNumber( elem, value, subtract ) {
+ var matches = rnumsplit.exec( value );
+ return matches ?
+ // Guard against undefined "subtract", e.g., when used as in cssHooks
+ Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
+ value;
+}
+
+function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
+ var i = extra === ( isBorderBox ? "border" : "content" ) ?
+ // If we already have the right measurement, avoid augmentation
+ 4 :
+ // Otherwise initialize for horizontal or vertical properties
+ name === "width" ? 1 : 0,
+
+ val = 0;
+
+ for ( ; i < 4; i += 2 ) {
+ // both box models exclude margin, so add it if we want it
+ if ( extra === "margin" ) {
+ val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
+ }
+
+ if ( isBorderBox ) {
+ // border-box includes padding, so remove it if we want content
+ if ( extra === "content" ) {
+ val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+ }
+
+ // at this point, extra isn't border nor margin, so remove border
+ if ( extra !== "margin" ) {
+ val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+ }
+ } else {
+ // at this point, extra isn't content, so add padding
+ val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+
+ // at this point, extra isn't content nor padding, so add border
+ if ( extra !== "padding" ) {
+ val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+ }
+ }
+ }
+
+ return val;
+}
+
+function getWidthOrHeight( elem, name, extra ) {
+
+ // Start with offset property, which is equivalent to the border-box value
+ var valueIsBorderBox = true,
+ val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+ styles = getStyles( elem ),
+ isBorderBox = support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
+
+ // some non-html elements return undefined for offsetWidth, so check for null/undefined
+ // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
+ // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
+ if ( val <= 0 || val == null ) {
+ // Fall back to computed then uncomputed css if necessary
+ val = curCSS( elem, name, styles );
+ if ( val < 0 || val == null ) {
+ val = elem.style[ name ];
+ }
+
+ // Computed unit is not pixels. Stop here and return.
+ if ( rnumnonpx.test(val) ) {
+ return val;
+ }
+
+ // we need the check for style in case a browser which returns unreliable values
+ // for getComputedStyle silently falls back to the reliable elem.style
+ valueIsBorderBox = isBorderBox && ( support.boxSizingReliable() || val === elem.style[ name ] );
+
+ // Normalize "", auto, and prepare for extra
+ val = parseFloat( val ) || 0;
+ }
+
+ // use the active box-sizing model to add/subtract irrelevant styles
+ return ( val +
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra || ( isBorderBox ? "border" : "content" ),
+ valueIsBorderBox,
+ styles
+ )
+ ) + "px";
+}
+
+jQuery.extend({
+ // Add in style property hooks for overriding the default
+ // behavior of getting and setting a style property
+ cssHooks: {
+ opacity: {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ // We should always get a number back from opacity
+ var ret = curCSS( elem, "opacity" );
+ return ret === "" ? "1" : ret;
+ }
+ }
+ }
+ },
+
+ // Don't automatically add "px" to these possibly-unitless properties
+ cssNumber: {
+ "columnCount": true,
+ "fillOpacity": true,
+ "flexGrow": true,
+ "flexShrink": true,
+ "fontWeight": true,
+ "lineHeight": true,
+ "opacity": true,
+ "order": true,
+ "orphans": true,
+ "widows": true,
+ "zIndex": true,
+ "zoom": true
+ },
+
+ // Add in properties whose names you wish to fix before
+ // setting or getting the value
+ cssProps: {
+ // normalize float css property
+ "float": support.cssFloat ? "cssFloat" : "styleFloat"
+ },
+
+ // Get and set the style property on a DOM Node
+ style: function( elem, name, value, extra ) {
+ // Don't set styles on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+ return;
+ }
+
+ // Make sure that we're working with the right name
+ var ret, type, hooks,
+ origName = jQuery.camelCase( name ),
+ style = elem.style;
+
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
+
+ // gets hook for the prefixed version
+ // followed by the unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // Check if we're setting a value
+ if ( value !== undefined ) {
+ type = typeof value;
+
+ // convert relative number strings (+= or -=) to relative numbers. #7345
+ if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+ value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
+ // Fixes bug #9237
+ type = "number";
+ }
+
+ // Make sure that null and NaN values aren't set. See: #7116
+ if ( value == null || value !== value ) {
+ return;
+ }
+
+ // If a number was passed in, add 'px' to the (except for certain CSS properties)
+ if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+ value += "px";
+ }
+
+ // Fixes #8908, it can be done more correctly by specifing setters in cssHooks,
+ // but it would mean to define eight (for every problematic property) identical functions
+ if ( !support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) {
+ style[ name ] = "inherit";
+ }
+
+ // If a hook was provided, use that value, otherwise just set the specified value
+ if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
+
+ // Support: IE
+ // Swallow errors from 'invalid' CSS values (#5509)
+ try {
+ style[ name ] = value;
+ } catch(e) {}
+ }
+
+ } else {
+ // If a hook was provided get the non-computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+ return ret;
+ }
+
+ // Otherwise just get the value from the style object
+ return style[ name ];
+ }
+ },
+
+ css: function( elem, name, extra, styles ) {
+ var num, val, hooks,
+ origName = jQuery.camelCase( name );
+
+ // Make sure that we're working with the right name
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
+
+ // gets hook for the prefixed version
+ // followed by the unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // If a hook was provided get the computed value from there
+ if ( hooks && "get" in hooks ) {
+ val = hooks.get( elem, true, extra );
+ }
+
+ // Otherwise, if a way to get the computed value exists, use that
+ if ( val === undefined ) {
+ val = curCSS( elem, name, styles );
+ }
+
+ //convert "normal" to computed value
+ if ( val === "normal" && name in cssNormalTransform ) {
+ val = cssNormalTransform[ name ];
+ }
+
+ // Return, converting to number if forced or a qualifier was provided and val looks numeric
+ if ( extra === "" || extra ) {
+ num = parseFloat( val );
+ return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
+ }
+ return val;
+ }
+});
+
+jQuery.each([ "height", "width" ], function( i, name ) {
+ jQuery.cssHooks[ name ] = {
+ get: function( elem, computed, extra ) {
+ if ( computed ) {
+ // certain elements can have dimension info if we invisibly show them
+ // however, it must have a current display style that would benefit from this
+ return rdisplayswap.test( jQuery.css( elem, "display" ) ) && elem.offsetWidth === 0 ?
+ jQuery.swap( elem, cssShow, function() {
+ return getWidthOrHeight( elem, name, extra );
+ }) :
+ getWidthOrHeight( elem, name, extra );
+ }
+ },
+
+ set: function( elem, value, extra ) {
+ var styles = extra && getStyles( elem );
+ return setPositiveNumber( elem, value, extra ?
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra,
+ support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
+ styles
+ ) : 0
+ );
+ }
+ };
+});
+
+if ( !support.opacity ) {
+ jQuery.cssHooks.opacity = {
+ get: function( elem, computed ) {
+ // IE uses filters for opacity
+ return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
+ ( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
+ computed ? "1" : "";
+ },
+
+ set: function( elem, value ) {
+ var style = elem.style,
+ currentStyle = elem.currentStyle,
+ opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
+ filter = currentStyle && currentStyle.filter || style.filter || "";
+
+ // IE has trouble with opacity if it does not have layout
+ // Force it by setting the zoom level
+ style.zoom = 1;
+
+ // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
+ // if value === "", then remove inline opacity #12685
+ if ( ( value >= 1 || value === "" ) &&
+ jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
+ style.removeAttribute ) {
+
+ // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
+ // if "filter:" is present at all, clearType is disabled, we want to avoid this
+ // style.removeAttribute is IE Only, but so apparently is this code path...
+ style.removeAttribute( "filter" );
+
+ // if there is no filter style applied in a css rule or unset inline opacity, we are done
+ if ( value === "" || currentStyle && !currentStyle.filter ) {
+ return;
+ }
+ }
+
+ // otherwise, set new filter values
+ style.filter = ralpha.test( filter ) ?
+ filter.replace( ralpha, opacity ) :
+ filter + " " + opacity;
+ }
+ };
+}
+
+jQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight,
+ function( elem, computed ) {
+ if ( computed ) {
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ // Work around by temporarily setting element display to inline-block
+ return jQuery.swap( elem, { "display": "inline-block" },
+ curCSS, [ elem, "marginRight" ] );
+ }
+ }
+);
+
+// These hooks are used by animate to expand properties
+jQuery.each({
+ margin: "",
+ padding: "",
+ border: "Width"
+}, function( prefix, suffix ) {
+ jQuery.cssHooks[ prefix + suffix ] = {
+ expand: function( value ) {
+ var i = 0,
+ expanded = {},
+
+ // assumes a single number if not a string
+ parts = typeof value === "string" ? value.split(" ") : [ value ];
+
+ for ( ; i < 4; i++ ) {
+ expanded[ prefix + cssExpand[ i ] + suffix ] =
+ parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+ }
+
+ return expanded;
+ }
+ };
+
+ if ( !rmargin.test( prefix ) ) {
+ jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+ }
+});
+
+jQuery.fn.extend({
+ css: function( name, value ) {
+ return access( this, function( elem, name, value ) {
+ var styles, len,
+ map = {},
+ i = 0;
+
+ if ( jQuery.isArray( name ) ) {
+ styles = getStyles( elem );
+ len = name.length;
+
+ for ( ; i < len; i++ ) {
+ map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
+ }
+
+ return map;
+ }
+
+ return value !== undefined ?
+ jQuery.style( elem, name, value ) :
+ jQuery.css( elem, name );
+ }, name, value, arguments.length > 1 );
+ },
+ show: function() {
+ return showHide( this, true );
+ },
+ hide: function() {
+ return showHide( this );
+ },
+ toggle: function( state ) {
+ if ( typeof state === "boolean" ) {
+ return state ? this.show() : this.hide();
+ }
+
+ return this.each(function() {
+ if ( isHidden( this ) ) {
+ jQuery( this ).show();
+ } else {
+ jQuery( this ).hide();
+ }
+ });
+ }
+});
+
+
+function Tween( elem, options, prop, end, easing ) {
+ return new Tween.prototype.init( elem, options, prop, end, easing );
+}
+jQuery.Tween = Tween;
+
+Tween.prototype = {
+ constructor: Tween,
+ init: function( elem, options, prop, end, easing, unit ) {
+ this.elem = elem;
+ this.prop = prop;
+ this.easing = easing || "swing";
+ this.options = options;
+ this.start = this.now = this.cur();
+ this.end = end;
+ this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+ },
+ cur: function() {
+ var hooks = Tween.propHooks[ this.prop ];
+
+ return hooks && hooks.get ?
+ hooks.get( this ) :
+ Tween.propHooks._default.get( this );
+ },
+ run: function( percent ) {
+ var eased,
+ hooks = Tween.propHooks[ this.prop ];
+
+ if ( this.options.duration ) {
+ this.pos = eased = jQuery.easing[ this.easing ](
+ percent, this.options.duration * percent, 0, 1, this.options.duration
+ );
+ } else {
+ this.pos = eased = percent;
+ }
+ this.now = ( this.end - this.start ) * eased + this.start;
+
+ if ( this.options.step ) {
+ this.options.step.call( this.elem, this.now, this );
+ }
+
+ if ( hooks && hooks.set ) {
+ hooks.set( this );
+ } else {
+ Tween.propHooks._default.set( this );
+ }
+ return this;
+ }
+};
+
+Tween.prototype.init.prototype = Tween.prototype;
+
+Tween.propHooks = {
+ _default: {
+ get: function( tween ) {
+ var result;
+
+ if ( tween.elem[ tween.prop ] != null &&
+ (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
+ return tween.elem[ tween.prop ];
+ }
+
+ // passing an empty string as a 3rd parameter to .css will automatically
+ // attempt a parseFloat and fallback to a string if the parse fails
+ // so, simple values such as "10px" are parsed to Float.
+ // complex values such as "rotate(1rad)" are returned as is.
+ result = jQuery.css( tween.elem, tween.prop, "" );
+ // Empty strings, null, undefined and "auto" are converted to 0.
+ return !result || result === "auto" ? 0 : result;
+ },
+ set: function( tween ) {
+ // use step hook for back compat - use cssHook if its there - use .style if its
+ // available and use plain properties where available
+ if ( jQuery.fx.step[ tween.prop ] ) {
+ jQuery.fx.step[ tween.prop ]( tween );
+ } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
+ jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+ } else {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+ }
+};
+
+// Support: IE <=9
+// Panic based approach to setting things on disconnected nodes
+
+Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+ set: function( tween ) {
+ if ( tween.elem.nodeType && tween.elem.parentNode ) {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+};
+
+jQuery.easing = {
+ linear: function( p ) {
+ return p;
+ },
+ swing: function( p ) {
+ return 0.5 - Math.cos( p * Math.PI ) / 2;
+ }
+};
+
+jQuery.fx = Tween.prototype.init;
+
+// Back Compat <1.8 extension point
+jQuery.fx.step = {};
+
+
+
+
+var
+ fxNow, timerId,
+ rfxtypes = /^(?:toggle|show|hide)$/,
+ rfxnum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ),
+ rrun = /queueHooks$/,
+ animationPrefilters = [ defaultPrefilter ],
+ tweeners = {
+ "*": [ function( prop, value ) {
+ var tween = this.createTween( prop, value ),
+ target = tween.cur(),
+ parts = rfxnum.exec( value ),
+ unit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
+
+ // Starting value computation is required for potential unit mismatches
+ start = ( jQuery.cssNumber[ prop ] || unit !== "px" && +target ) &&
+ rfxnum.exec( jQuery.css( tween.elem, prop ) ),
+ scale = 1,
+ maxIterations = 20;
+
+ if ( start && start[ 3 ] !== unit ) {
+ // Trust units reported by jQuery.css
+ unit = unit || start[ 3 ];
+
+ // Make sure we update the tween properties later on
+ parts = parts || [];
+
+ // Iteratively approximate from a nonzero starting point
+ start = +target || 1;
+
+ do {
+ // If previous iteration zeroed out, double until we get *something*
+ // Use a string for doubling factor so we don't accidentally see scale as unchanged below
+ scale = scale || ".5";
+
+ // Adjust and apply
+ start = start / scale;
+ jQuery.style( tween.elem, prop, start + unit );
+
+ // Update scale, tolerating zero or NaN from tween.cur()
+ // And breaking the loop if scale is unchanged or perfect, or if we've just had enough
+ } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
+ }
+
+ // Update tween properties
+ if ( parts ) {
+ start = tween.start = +start || +target || 0;
+ tween.unit = unit;
+ // If a +=/-= token was provided, we're doing a relative animation
+ tween.end = parts[ 1 ] ?
+ start + ( parts[ 1 ] + 1 ) * parts[ 2 ] :
+ +parts[ 2 ];
+ }
+
+ return tween;
+ } ]
+ };
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+ setTimeout(function() {
+ fxNow = undefined;
+ });
+ return ( fxNow = jQuery.now() );
+}
+
+// Generate parameters to create a standard animation
+function genFx( type, includeWidth ) {
+ var which,
+ attrs = { height: type },
+ i = 0;
+
+ // if we include width, step value is 1 to do all cssExpand values,
+ // if we don't include width, step value is 2 to skip over Left and Right
+ includeWidth = includeWidth ? 1 : 0;
+ for ( ; i < 4 ; i += 2 - includeWidth ) {
+ which = cssExpand[ i ];
+ attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+ }
+
+ if ( includeWidth ) {
+ attrs.opacity = attrs.width = type;
+ }
+
+ return attrs;
+}
+
+function createTween( value, prop, animation ) {
+ var tween,
+ collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
+ index = 0,
+ length = collection.length;
+ for ( ; index < length; index++ ) {
+ if ( (tween = collection[ index ].call( animation, prop, value )) ) {
+
+ // we're done with this property
+ return tween;
+ }
+ }
+}
+
+function defaultPrefilter( elem, props, opts ) {
+ /* jshint validthis: true */
+ var prop, value, toggle, tween, hooks, oldfire, display, checkDisplay,
+ anim = this,
+ orig = {},
+ style = elem.style,
+ hidden = elem.nodeType && isHidden( elem ),
+ dataShow = jQuery._data( elem, "fxshow" );
+
+ // handle queue: false promises
+ if ( !opts.queue ) {
+ hooks = jQuery._queueHooks( elem, "fx" );
+ if ( hooks.unqueued == null ) {
+ hooks.unqueued = 0;
+ oldfire = hooks.empty.fire;
+ hooks.empty.fire = function() {
+ if ( !hooks.unqueued ) {
+ oldfire();
+ }
+ };
+ }
+ hooks.unqueued++;
+
+ anim.always(function() {
+ // doing this makes sure that the complete handler will be called
+ // before this completes
+ anim.always(function() {
+ hooks.unqueued--;
+ if ( !jQuery.queue( elem, "fx" ).length ) {
+ hooks.empty.fire();
+ }
+ });
+ });
+ }
+
+ // height/width overflow pass
+ if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
+ // Make sure that nothing sneaks out
+ // Record all 3 overflow attributes because IE does not
+ // change the overflow attribute when overflowX and
+ // overflowY are set to the same value
+ opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
+
+ // Set display property to inline-block for height/width
+ // animations on inline elements that are having width/height animated
+ display = jQuery.css( elem, "display" );
+
+ // Test default display if display is currently "none"
+ checkDisplay = display === "none" ?
+ jQuery._data( elem, "olddisplay" ) || defaultDisplay( elem.nodeName ) : display;
+
+ if ( checkDisplay === "inline" && jQuery.css( elem, "float" ) === "none" ) {
+
+ // inline-level elements accept inline-block;
+ // block-level elements need to be inline with layout
+ if ( !support.inlineBlockNeedsLayout || defaultDisplay( elem.nodeName ) === "inline" ) {
+ style.display = "inline-block";
+ } else {
+ style.zoom = 1;
+ }
+ }
+ }
+
+ if ( opts.overflow ) {
+ style.overflow = "hidden";
+ if ( !support.shrinkWrapBlocks() ) {
+ anim.always(function() {
+ style.overflow = opts.overflow[ 0 ];
+ style.overflowX = opts.overflow[ 1 ];
+ style.overflowY = opts.overflow[ 2 ];
+ });
+ }
+ }
+
+ // show/hide pass
+ for ( prop in props ) {
+ value = props[ prop ];
+ if ( rfxtypes.exec( value ) ) {
+ delete props[ prop ];
+ toggle = toggle || value === "toggle";
+ if ( value === ( hidden ? "hide" : "show" ) ) {
+
+ // If there is dataShow left over from a stopped hide or show and we are going to proceed with show, we should pretend to be hidden
+ if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
+ hidden = true;
+ } else {
+ continue;
+ }
+ }
+ orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
+
+ // Any non-fx value stops us from restoring the original display value
+ } else {
+ display = undefined;
+ }
+ }
+
+ if ( !jQuery.isEmptyObject( orig ) ) {
+ if ( dataShow ) {
+ if ( "hidden" in dataShow ) {
+ hidden = dataShow.hidden;
+ }
+ } else {
+ dataShow = jQuery._data( elem, "fxshow", {} );
+ }
+
+ // store state if its toggle - enables .stop().toggle() to "reverse"
+ if ( toggle ) {
+ dataShow.hidden = !hidden;
+ }
+ if ( hidden ) {
+ jQuery( elem ).show();
+ } else {
+ anim.done(function() {
+ jQuery( elem ).hide();
+ });
+ }
+ anim.done(function() {
+ var prop;
+ jQuery._removeData( elem, "fxshow" );
+ for ( prop in orig ) {
+ jQuery.style( elem, prop, orig[ prop ] );
+ }
+ });
+ for ( prop in orig ) {
+ tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
+
+ if ( !( prop in dataShow ) ) {
+ dataShow[ prop ] = tween.start;
+ if ( hidden ) {
+ tween.end = tween.start;
+ tween.start = prop === "width" || prop === "height" ? 1 : 0;
+ }
+ }
+ }
+
+ // If this is a noop like .hide().hide(), restore an overwritten display value
+ } else if ( (display === "none" ? defaultDisplay( elem.nodeName ) : display) === "inline" ) {
+ style.display = display;
+ }
+}
+
+function propFilter( props, specialEasing ) {
+ var index, name, easing, value, hooks;
+
+ // camelCase, specialEasing and expand cssHook pass
+ for ( index in props ) {
+ name = jQuery.camelCase( index );
+ easing = specialEasing[ name ];
+ value = props[ index ];
+ if ( jQuery.isArray( value ) ) {
+ easing = value[ 1 ];
+ value = props[ index ] = value[ 0 ];
+ }
+
+ if ( index !== name ) {
+ props[ name ] = value;
+ delete props[ index ];
+ }
+
+ hooks = jQuery.cssHooks[ name ];
+ if ( hooks && "expand" in hooks ) {
+ value = hooks.expand( value );
+ delete props[ name ];
+
+ // not quite $.extend, this wont overwrite keys already present.
+ // also - reusing 'index' from above because we have the correct "name"
+ for ( index in value ) {
+ if ( !( index in props ) ) {
+ props[ index ] = value[ index ];
+ specialEasing[ index ] = easing;
+ }
+ }
+ } else {
+ specialEasing[ name ] = easing;
+ }
+ }
+}
+
+function Animation( elem, properties, options ) {
+ var result,
+ stopped,
+ index = 0,
+ length = animationPrefilters.length,
+ deferred = jQuery.Deferred().always( function() {
+ // don't match elem in the :animated selector
+ delete tick.elem;
+ }),
+ tick = function() {
+ if ( stopped ) {
+ return false;
+ }
+ var currentTime = fxNow || createFxNow(),
+ remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+ // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
+ temp = remaining / animation.duration || 0,
+ percent = 1 - temp,
+ index = 0,
+ length = animation.tweens.length;
+
+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( percent );
+ }
+
+ deferred.notifyWith( elem, [ animation, percent, remaining ]);
+
+ if ( percent < 1 && length ) {
+ return remaining;
+ } else {
+ deferred.resolveWith( elem, [ animation ] );
+ return false;
+ }
+ },
+ animation = deferred.promise({
+ elem: elem,
+ props: jQuery.extend( {}, properties ),
+ opts: jQuery.extend( true, { specialEasing: {} }, options ),
+ originalProperties: properties,
+ originalOptions: options,
+ startTime: fxNow || createFxNow(),
+ duration: options.duration,
+ tweens: [],
+ createTween: function( prop, end ) {
+ var tween = jQuery.Tween( elem, animation.opts, prop, end,
+ animation.opts.specialEasing[ prop ] || animation.opts.easing );
+ animation.tweens.push( tween );
+ return tween;
+ },
+ stop: function( gotoEnd ) {
+ var index = 0,
+ // if we are going to the end, we want to run all the tweens
+ // otherwise we skip this part
+ length = gotoEnd ? animation.tweens.length : 0;
+ if ( stopped ) {
+ return this;
+ }
+ stopped = true;
+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( 1 );
+ }
+
+ // resolve when we played the last frame
+ // otherwise, reject
+ if ( gotoEnd ) {
+ deferred.resolveWith( elem, [ animation, gotoEnd ] );
+ } else {
+ deferred.rejectWith( elem, [ animation, gotoEnd ] );
+ }
+ return this;
+ }
+ }),
+ props = animation.props;
+
+ propFilter( props, animation.opts.specialEasing );
+
+ for ( ; index < length ; index++ ) {
+ result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
+ if ( result ) {
+ return result;
+ }
+ }
+
+ jQuery.map( props, createTween, animation );
+
+ if ( jQuery.isFunction( animation.opts.start ) ) {
+ animation.opts.start.call( elem, animation );
+ }
+
+ jQuery.fx.timer(
+ jQuery.extend( tick, {
+ elem: elem,
+ anim: animation,
+ queue: animation.opts.queue
+ })
+ );
+
+ // attach callbacks from options
+ return animation.progress( animation.opts.progress )
+ .done( animation.opts.done, animation.opts.complete )
+ .fail( animation.opts.fail )
+ .always( animation.opts.always );
+}
+
+jQuery.Animation = jQuery.extend( Animation, {
+ tweener: function( props, callback ) {
+ if ( jQuery.isFunction( props ) ) {
+ callback = props;
+ props = [ "*" ];
+ } else {
+ props = props.split(" ");
+ }
+
+ var prop,
+ index = 0,
+ length = props.length;
+
+ for ( ; index < length ; index++ ) {
+ prop = props[ index ];
+ tweeners[ prop ] = tweeners[ prop ] || [];
+ tweeners[ prop ].unshift( callback );
+ }
+ },
+
+ prefilter: function( callback, prepend ) {
+ if ( prepend ) {
+ animationPrefilters.unshift( callback );
+ } else {
+ animationPrefilters.push( callback );
+ }
+ }
+});
+
+jQuery.speed = function( speed, easing, fn ) {
+ var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+ };
+
+ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+ opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+ // normalize opt.queue - true/undefined/null -> "fx"
+ if ( opt.queue == null || opt.queue === true ) {
+ opt.queue = "fx";
+ }
+
+ // Queueing
+ opt.old = opt.complete;
+
+ opt.complete = function() {
+ if ( jQuery.isFunction( opt.old ) ) {
+ opt.old.call( this );
+ }
+
+ if ( opt.queue ) {
+ jQuery.dequeue( this, opt.queue );
+ }
+ };
+
+ return opt;
+};
+
+jQuery.fn.extend({
+ fadeTo: function( speed, to, easing, callback ) {
+
+ // show any hidden elements after setting opacity to 0
+ return this.filter( isHidden ).css( "opacity", 0 ).show()
+
+ // animate to the value specified
+ .end().animate({ opacity: to }, speed, easing, callback );
+ },
+ animate: function( prop, speed, easing, callback ) {
+ var empty = jQuery.isEmptyObject( prop ),
+ optall = jQuery.speed( speed, easing, callback ),
+ doAnimation = function() {
+ // Operate on a copy of prop so per-property easing won't be lost
+ var anim = Animation( this, jQuery.extend( {}, prop ), optall );
+
+ // Empty animations, or finishing resolves immediately
+ if ( empty || jQuery._data( this, "finish" ) ) {
+ anim.stop( true );
+ }
+ };
+ doAnimation.finish = doAnimation;
+
+ return empty || optall.queue === false ?
+ this.each( doAnimation ) :
+ this.queue( optall.queue, doAnimation );
+ },
+ stop: function( type, clearQueue, gotoEnd ) {
+ var stopQueue = function( hooks ) {
+ var stop = hooks.stop;
+ delete hooks.stop;
+ stop( gotoEnd );
+ };
+
+ if ( typeof type !== "string" ) {
+ gotoEnd = clearQueue;
+ clearQueue = type;
+ type = undefined;
+ }
+ if ( clearQueue && type !== false ) {
+ this.queue( type || "fx", [] );
+ }
+
+ return this.each(function() {
+ var dequeue = true,
+ index = type != null && type + "queueHooks",
+ timers = jQuery.timers,
+ data = jQuery._data( this );
+
+ if ( index ) {
+ if ( data[ index ] && data[ index ].stop ) {
+ stopQueue( data[ index ] );
+ }
+ } else {
+ for ( index in data ) {
+ if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+ stopQueue( data[ index ] );
+ }
+ }
+ }
+
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+ timers[ index ].anim.stop( gotoEnd );
+ dequeue = false;
+ timers.splice( index, 1 );
+ }
+ }
+
+ // start the next in the queue if the last step wasn't forced
+ // timers currently will call their complete callbacks, which will dequeue
+ // but only if they were gotoEnd
+ if ( dequeue || !gotoEnd ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ finish: function( type ) {
+ if ( type !== false ) {
+ type = type || "fx";
+ }
+ return this.each(function() {
+ var index,
+ data = jQuery._data( this ),
+ queue = data[ type + "queue" ],
+ hooks = data[ type + "queueHooks" ],
+ timers = jQuery.timers,
+ length = queue ? queue.length : 0;
+
+ // enable finishing flag on private data
+ data.finish = true;
+
+ // empty the queue first
+ jQuery.queue( this, type, [] );
+
+ if ( hooks && hooks.stop ) {
+ hooks.stop.call( this, true );
+ }
+
+ // look for any active animations, and finish them
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
+ timers[ index ].anim.stop( true );
+ timers.splice( index, 1 );
+ }
+ }
+
+ // look for any animations in the old queue and finish them
+ for ( index = 0; index < length; index++ ) {
+ if ( queue[ index ] && queue[ index ].finish ) {
+ queue[ index ].finish.call( this );
+ }
+ }
+
+ // turn off finishing flag
+ delete data.finish;
+ });
+ }
+});
+
+jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
+ var cssFn = jQuery.fn[ name ];
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return speed == null || typeof speed === "boolean" ?
+ cssFn.apply( this, arguments ) :
+ this.animate( genFx( name, true ), speed, easing, callback );
+ };
+});
+
+// Generate shortcuts for custom animations
+jQuery.each({
+ slideDown: genFx("show"),
+ slideUp: genFx("hide"),
+ slideToggle: genFx("toggle"),
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" },
+ fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return this.animate( props, speed, easing, callback );
+ };
+});
+
+jQuery.timers = [];
+jQuery.fx.tick = function() {
+ var timer,
+ timers = jQuery.timers,
+ i = 0;
+
+ fxNow = jQuery.now();
+
+ for ( ; i < timers.length; i++ ) {
+ timer = timers[ i ];
+ // Checks the timer has not already been removed
+ if ( !timer() && timers[ i ] === timer ) {
+ timers.splice( i--, 1 );
+ }
+ }
+
+ if ( !timers.length ) {
+ jQuery.fx.stop();
+ }
+ fxNow = undefined;
+};
+
+jQuery.fx.timer = function( timer ) {
+ jQuery.timers.push( timer );
+ if ( timer() ) {
+ jQuery.fx.start();
+ } else {
+ jQuery.timers.pop();
+ }
+};
+
+jQuery.fx.interval = 13;
+
+jQuery.fx.start = function() {
+ if ( !timerId ) {
+ timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
+ }
+};
+
+jQuery.fx.stop = function() {
+ clearInterval( timerId );
+ timerId = null;
+};
+
+jQuery.fx.speeds = {
+ slow: 600,
+ fast: 200,
+ // Default speed
+ _default: 400
+};
+
+
+// Based off of the plugin by Clint Helfers, with permission.
+// http://blindsignals.com/index.php/2009/07/jquery-delay/
+jQuery.fn.delay = function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+};
+
+
+(function() {
+ // Minified: var a,b,c,d,e
+ var input, div, select, a, opt;
+
+ // Setup
+ div = document.createElement( "div" );
+ div.setAttribute( "className", "t" );
+ div.innerHTML = " a ";
+ a = div.getElementsByTagName("a")[ 0 ];
+
+ // First batch of tests.
+ select = document.createElement("select");
+ opt = select.appendChild( document.createElement("option") );
+ input = div.getElementsByTagName("input")[ 0 ];
+
+ a.style.cssText = "top:1px";
+
+ // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+ support.getSetAttribute = div.className !== "t";
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText instead)
+ support.style = /top/.test( a.getAttribute("style") );
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ support.hrefNormalized = a.getAttribute("href") === "/a";
+
+ // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere)
+ support.checkOn = !!input.value;
+
+ // Make sure that a selected-by-default option has a working selected property.
+ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+ support.optSelected = opt.selected;
+
+ // Tests for enctype support on a form (#6743)
+ support.enctype = !!document.createElement("form").enctype;
+
+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as disabled)
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
+
+ // Support: IE8 only
+ // Check if we can trust getAttribute("value")
+ input = document.createElement( "input" );
+ input.setAttribute( "value", "" );
+ support.input = input.getAttribute( "value" ) === "";
+
+ // Check if an input maintains its value after becoming a radio
+ input.value = "t";
+ input.setAttribute( "type", "radio" );
+ support.radioValue = input.value === "t";
+})();
+
+
+var rreturn = /\r/g;
+
+jQuery.fn.extend({
+ val: function( value ) {
+ var hooks, ret, isFunction,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var val;
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, jQuery( this ).val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+ } else if ( typeof val === "number" ) {
+ val += "";
+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map( val, function( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ var val = jQuery.find.attr( elem, "value" );
+ return val != null ?
+ val :
+ // Support: IE10-11+
+ // option.text throws exceptions (#14686, #14858)
+ jQuery.trim( jQuery.text( elem ) );
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, option,
+ options = elem.options,
+ index = elem.selectedIndex,
+ one = elem.type === "select-one" || index < 0,
+ values = one ? null : [],
+ max = one ? index + 1 : options.length,
+ i = index < 0 ?
+ max :
+ one ? index : 0;
+
+ // Loop through all the selected options
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // oldIE doesn't update selected after form reset (#2551)
+ if ( ( option.selected || i === index ) &&
+ // Don't return options that are disabled or in a disabled optgroup
+ ( support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
+ ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var optionSet, option,
+ options = elem.options,
+ values = jQuery.makeArray( value ),
+ i = options.length;
+
+ while ( i-- ) {
+ option = options[ i ];
+
+ if ( jQuery.inArray( jQuery.valHooks.option.get( option ), values ) >= 0 ) {
+
+ // Support: IE6
+ // When new option element is added to select box we need to
+ // force reflow of newly added node in order to workaround delay
+ // of initialization properties
+ try {
+ option.selected = optionSet = true;
+
+ } catch ( _ ) {
+
+ // Will be executed only in IE6
+ option.scrollHeight;
+ }
+
+ } else {
+ option.selected = false;
+ }
+ }
+
+ // Force browsers to behave consistently when non-matching value is set
+ if ( !optionSet ) {
+ elem.selectedIndex = -1;
+ }
+
+ return options;
+ }
+ }
+ }
+});
+
+// Radios and checkboxes getter/setter
+jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+ }
+ }
+ };
+ if ( !support.checkOn ) {
+ jQuery.valHooks[ this ].get = function( elem ) {
+ // Support: Webkit
+ // "" is returned instead of "on" if a value isn't specified
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ };
+ }
+});
+
+
+
+
+var nodeHook, boolHook,
+ attrHandle = jQuery.expr.attrHandle,
+ ruseDefault = /^(?:checked|selected)$/i,
+ getSetAttribute = support.getSetAttribute,
+ getSetInput = support.input;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return access( this, jQuery.attr, name, value, arguments.length > 1 );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ }
+});
+
+jQuery.extend({
+ attr: function( elem, name, value ) {
+ var hooks, ret,
+ nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === strundefined ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ // All attributes are lowercase
+ // Grab necessary hook if one is defined
+ if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+ name = name.toLowerCase();
+ hooks = jQuery.attrHooks[ name ] ||
+ ( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+
+ } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, value + "" );
+ return value;
+ }
+
+ } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ ret = jQuery.find.attr( elem, name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret == null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var name, propName,
+ i = 0,
+ attrNames = value && value.match( rnotwhite );
+
+ if ( attrNames && elem.nodeType === 1 ) {
+ while ( (name = attrNames[i++]) ) {
+ propName = jQuery.propFix[ name ] || name;
+
+ // Boolean attributes get special treatment (#10870)
+ if ( jQuery.expr.match.bool.test( name ) ) {
+ // Set corresponding property to false
+ if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
+ elem[ propName ] = false;
+ // Support: IE<9
+ // Also clear defaultChecked/defaultSelected (if appropriate)
+ } else {
+ elem[ jQuery.camelCase( "default-" + name ) ] =
+ elem[ propName ] = false;
+ }
+
+ // See #9699 for explanation of this approach (setting first, then removal)
+ } else {
+ jQuery.attr( elem, name, "" );
+ }
+
+ elem.removeAttribute( getSetAttribute ? name : propName );
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ if ( !support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+ // Setting the type on a radio button after the value resets the value in IE6-9
+ // Reset value to default in case type is set after value during creation
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ }
+ }
+});
+
+// Hook for boolean attributes
+boolHook = {
+ set: function( elem, value, name ) {
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
+ // IE<8 needs the *property* name
+ elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name );
+
+ // Use defaultChecked and defaultSelected for oldIE
+ } else {
+ elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true;
+ }
+
+ return name;
+ }
+};
+
+// Retrieve booleans specially
+jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
+
+ var getter = attrHandle[ name ] || jQuery.find.attr;
+
+ attrHandle[ name ] = getSetInput && getSetAttribute || !ruseDefault.test( name ) ?
+ function( elem, name, isXML ) {
+ var ret, handle;
+ if ( !isXML ) {
+ // Avoid an infinite loop by temporarily removing this function from the getter
+ handle = attrHandle[ name ];
+ attrHandle[ name ] = ret;
+ ret = getter( elem, name, isXML ) != null ?
+ name.toLowerCase() :
+ null;
+ attrHandle[ name ] = handle;
+ }
+ return ret;
+ } :
+ function( elem, name, isXML ) {
+ if ( !isXML ) {
+ return elem[ jQuery.camelCase( "default-" + name ) ] ?
+ name.toLowerCase() :
+ null;
+ }
+ };
+});
+
+// fix oldIE attroperties
+if ( !getSetInput || !getSetAttribute ) {
+ jQuery.attrHooks.value = {
+ set: function( elem, value, name ) {
+ if ( jQuery.nodeName( elem, "input" ) ) {
+ // Does not return so that setAttribute is also used
+ elem.defaultValue = value;
+ } else {
+ // Use nodeHook if defined (#1954); otherwise setAttribute is fine
+ return nodeHook && nodeHook.set( elem, value, name );
+ }
+ }
+ };
+}
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+ // Use this for any attribute in IE6/7
+ // This fixes almost every IE6/7 issue
+ nodeHook = {
+ set: function( elem, value, name ) {
+ // Set the existing or create a new attribute node
+ var ret = elem.getAttributeNode( name );
+ if ( !ret ) {
+ elem.setAttributeNode(
+ (ret = elem.ownerDocument.createAttribute( name ))
+ );
+ }
+
+ ret.value = value += "";
+
+ // Break association with cloned elements by also using setAttribute (#9646)
+ if ( name === "value" || value === elem.getAttribute( name ) ) {
+ return value;
+ }
+ }
+ };
+
+ // Some attributes are constructed with empty-string values when not defined
+ attrHandle.id = attrHandle.name = attrHandle.coords =
+ function( elem, name, isXML ) {
+ var ret;
+ if ( !isXML ) {
+ return (ret = elem.getAttributeNode( name )) && ret.value !== "" ?
+ ret.value :
+ null;
+ }
+ };
+
+ // Fixing value retrieval on a button requires this module
+ jQuery.valHooks.button = {
+ get: function( elem, name ) {
+ var ret = elem.getAttributeNode( name );
+ if ( ret && ret.specified ) {
+ return ret.value;
+ }
+ },
+ set: nodeHook.set
+ };
+
+ // Set contenteditable to false on removals(#10429)
+ // Setting to empty string throws an error as an invalid value
+ jQuery.attrHooks.contenteditable = {
+ set: function( elem, value, name ) {
+ nodeHook.set( elem, value === "" ? false : value, name );
+ }
+ };
+
+ // Set width and height to auto instead of 0 on empty string( Bug #8150 )
+ // This is for removals
+ jQuery.each([ "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = {
+ set: function( elem, value ) {
+ if ( value === "" ) {
+ elem.setAttribute( name, "auto" );
+ return value;
+ }
+ }
+ };
+ });
+}
+
+if ( !support.style ) {
+ jQuery.attrHooks.style = {
+ get: function( elem ) {
+ // Return undefined in the case of empty string
+ // Note: IE uppercases css property names, but if we were to .toLowerCase()
+ // .cssText, that would destroy case senstitivity in URL's, like in "background"
+ return elem.style.cssText || undefined;
+ },
+ set: function( elem, value ) {
+ return ( elem.style.cssText = value + "" );
+ }
+ };
+}
+
+
+
+
+var rfocusable = /^(?:input|select|textarea|button|object)$/i,
+ rclickable = /^(?:a|area)$/i;
+
+jQuery.fn.extend({
+ prop: function( name, value ) {
+ return access( this, jQuery.prop, name, value, arguments.length > 1 );
+ },
+
+ removeProp: function( name ) {
+ name = jQuery.propFix[ name ] || name;
+ return this.each(function() {
+ // try/catch handles cases where IE balks (such as removing a property on window)
+ try {
+ this[ name ] = undefined;
+ delete this[ name ];
+ } catch( e ) {}
+ });
+ }
+});
+
+jQuery.extend({
+ propFix: {
+ "for": "htmlFor",
+ "class": "className"
+ },
+
+ prop: function( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?
+ ret :
+ ( elem[ name ] = value );
+
+ } else {
+ return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ?
+ ret :
+ elem[ name ];
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ // Use proper attribute retrieval(#12072)
+ var tabindex = jQuery.find.attr( elem, "tabindex" );
+
+ return tabindex ?
+ parseInt( tabindex, 10 ) :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ -1;
+ }
+ }
+ }
+});
+
+// Some attributes require a special call on IE
+// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+if ( !support.hrefNormalized ) {
+ // href/src property should get the full normalized URL (#10299/#12915)
+ jQuery.each([ "href", "src" ], function( i, name ) {
+ jQuery.propHooks[ name ] = {
+ get: function( elem ) {
+ return elem.getAttribute( name, 4 );
+ }
+ };
+ });
+}
+
+// Support: Safari, IE9+
+// mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !support.optSelected ) {
+ jQuery.propHooks.selected = {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ return null;
+ }
+ };
+}
+
+jQuery.each([
+ "tabIndex",
+ "readOnly",
+ "maxLength",
+ "cellSpacing",
+ "cellPadding",
+ "rowSpan",
+ "colSpan",
+ "useMap",
+ "frameBorder",
+ "contentEditable"
+], function() {
+ jQuery.propFix[ this.toLowerCase() ] = this;
+});
+
+// IE6/7 call enctype encoding
+if ( !support.enctype ) {
+ jQuery.propFix.enctype = "encoding";
+}
+
+
+
+
+var rclass = /[\t\r\n\f]/g;
+
+jQuery.fn.extend({
+ addClass: function( value ) {
+ var classes, elem, cur, clazz, j, finalValue,
+ i = 0,
+ len = this.length,
+ proceed = typeof value === "string" && value;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call( this, j, this.className ) );
+ });
+ }
+
+ if ( proceed ) {
+ // The disjunction here is for better compressibility (see removeClass)
+ classes = ( value || "" ).match( rnotwhite ) || [];
+
+ for ( ; i < len; i++ ) {
+ elem = this[ i ];
+ cur = elem.nodeType === 1 && ( elem.className ?
+ ( " " + elem.className + " " ).replace( rclass, " " ) :
+ " "
+ );
+
+ if ( cur ) {
+ j = 0;
+ while ( (clazz = classes[j++]) ) {
+ if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
+ cur += clazz + " ";
+ }
+ }
+
+ // only assign if different to avoid unneeded rendering.
+ finalValue = jQuery.trim( cur );
+ if ( elem.className !== finalValue ) {
+ elem.className = finalValue;
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var classes, elem, cur, clazz, j, finalValue,
+ i = 0,
+ len = this.length,
+ proceed = arguments.length === 0 || typeof value === "string" && value;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call( this, j, this.className ) );
+ });
+ }
+ if ( proceed ) {
+ classes = ( value || "" ).match( rnotwhite ) || [];
+
+ for ( ; i < len; i++ ) {
+ elem = this[ i ];
+ // This expression is here for better compressibility (see addClass)
+ cur = elem.nodeType === 1 && ( elem.className ?
+ ( " " + elem.className + " " ).replace( rclass, " " ) :
+ ""
+ );
+
+ if ( cur ) {
+ j = 0;
+ while ( (clazz = classes[j++]) ) {
+ // Remove *all* instances
+ while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
+ cur = cur.replace( " " + clazz + " ", " " );
+ }
+ }
+
+ // only assign if different to avoid unneeded rendering.
+ finalValue = value ? jQuery.trim( cur ) : "";
+ if ( elem.className !== finalValue ) {
+ elem.className = finalValue;
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value;
+
+ if ( typeof stateVal === "boolean" && type === "string" ) {
+ return stateVal ? this.addClass( value ) : this.removeClass( value );
+ }
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ classNames = value.match( rnotwhite ) || [];
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space separated list
+ if ( self.hasClass( className ) ) {
+ self.removeClass( className );
+ } else {
+ self.addClass( className );
+ }
+ }
+
+ // Toggle whole class name
+ } else if ( type === strundefined || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery._data( this, "__className__", this.className );
+ }
+
+ // If the element has a class name or if we're passed "false",
+ // then remove the whole classname (if there was one, the above saved it).
+ // Otherwise bring back whatever was previously saved (if anything),
+ // falling back to the empty string if nothing was stored.
+ this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ",
+ i = 0,
+ l = this.length;
+ for ( ; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+});
+
+
+
+
+// Return jQuery for attributes-only inclusion
+
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ return arguments.length > 0 ?
+ this.on( name, null, data, fn ) :
+ this.trigger( name );
+ };
+});
+
+jQuery.fn.extend({
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ },
+
+ bind: function( types, data, fn ) {
+ return this.on( types, null, data, fn );
+ },
+ unbind: function( types, fn ) {
+ return this.off( types, null, fn );
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.on( types, selector, data, fn );
+ },
+ undelegate: function( selector, types, fn ) {
+ // ( namespace ) or ( selector, types [, fn] )
+ return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
+ }
+});
+
+
+var nonce = jQuery.now();
+
+var rquery = (/\?/);
+
+
+
+var rvalidtokens = /(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;
+
+jQuery.parseJSON = function( data ) {
+ // Attempt to parse using the native JSON parser first
+ if ( window.JSON && window.JSON.parse ) {
+ // Support: Android 2.3
+ // Workaround failure to string-cast null input
+ return window.JSON.parse( data + "" );
+ }
+
+ var requireNonComma,
+ depth = null,
+ str = jQuery.trim( data + "" );
+
+ // Guard against invalid (and possibly dangerous) input by ensuring that nothing remains
+ // after removing valid tokens
+ return str && !jQuery.trim( str.replace( rvalidtokens, function( token, comma, open, close ) {
+
+ // Force termination if we see a misplaced comma
+ if ( requireNonComma && comma ) {
+ depth = 0;
+ }
+
+ // Perform no more replacements after returning to outermost depth
+ if ( depth === 0 ) {
+ return token;
+ }
+
+ // Commas must not follow "[", "{", or ","
+ requireNonComma = open || comma;
+
+ // Determine new depth
+ // array/object open ("[" or "{"): depth += true - false (increment)
+ // array/object close ("]" or "}"): depth += false - true (decrement)
+ // other cases ("," or primitive): depth += true - true (numeric cast)
+ depth += !close - !open;
+
+ // Remove this token
+ return "";
+ }) ) ?
+ ( Function( "return " + str ) )() :
+ jQuery.error( "Invalid JSON: " + data );
+};
+
+
+// Cross-browser xml parsing
+jQuery.parseXML = function( data ) {
+ var xml, tmp;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+ try {
+ if ( window.DOMParser ) { // Standard
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data, "text/xml" );
+ } else { // IE
+ xml = new ActiveXObject( "Microsoft.XMLDOM" );
+ xml.async = "false";
+ xml.loadXML( data );
+ }
+ } catch( e ) {
+ xml = undefined;
+ }
+ if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+};
+
+
+var
+ // Document location
+ ajaxLocParts,
+ ajaxLocation,
+
+ rhash = /#.*$/,
+ rts = /([?&])_=[^&]*/,
+ rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+ // #7653, #8125, #8152: local protocol detection
+ rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
+ rnoContent = /^(?:GET|HEAD)$/,
+ rprotocol = /^\/\//,
+ rurl = /^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,
+
+ /* Prefilters
+ * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+ * 2) These are called:
+ * - BEFORE asking for a transport
+ * - AFTER param serialization (s.data is a string if s.processData is true)
+ * 3) key is the dataType
+ * 4) the catchall symbol "*" can be used
+ * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+ */
+ prefilters = {},
+
+ /* Transports bindings
+ * 1) key is the dataType
+ * 2) the catchall symbol "*" can be used
+ * 3) selection will start with transport dataType and THEN go to "*" if needed
+ */
+ transports = {},
+
+ // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+ allTypes = "*/".concat("*");
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+ ajaxLocation = location.href;
+} catch( e ) {
+ // Use the href attribute of an A element
+ // since IE will modify it given document.location
+ ajaxLocation = document.createElement( "a" );
+ ajaxLocation.href = "";
+ ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+ // dataTypeExpression is optional and defaults to "*"
+ return function( dataTypeExpression, func ) {
+
+ if ( typeof dataTypeExpression !== "string" ) {
+ func = dataTypeExpression;
+ dataTypeExpression = "*";
+ }
+
+ var dataType,
+ i = 0,
+ dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];
+
+ if ( jQuery.isFunction( func ) ) {
+ // For each dataType in the dataTypeExpression
+ while ( (dataType = dataTypes[i++]) ) {
+ // Prepend if requested
+ if ( dataType.charAt( 0 ) === "+" ) {
+ dataType = dataType.slice( 1 ) || "*";
+ (structure[ dataType ] = structure[ dataType ] || []).unshift( func );
+
+ // Otherwise append
+ } else {
+ (structure[ dataType ] = structure[ dataType ] || []).push( func );
+ }
+ }
+ }
+ };
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
+
+ var inspected = {},
+ seekingTransport = ( structure === transports );
+
+ function inspect( dataType ) {
+ var selected;
+ inspected[ dataType ] = true;
+ jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
+ var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
+ if ( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
+ options.dataTypes.unshift( dataTypeOrTransport );
+ inspect( dataTypeOrTransport );
+ return false;
+ } else if ( seekingTransport ) {
+ return !( selected = dataTypeOrTransport );
+ }
+ });
+ return selected;
+ }
+
+ return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+ var deep, key,
+ flatOptions = jQuery.ajaxSettings.flatOptions || {};
+
+ for ( key in src ) {
+ if ( src[ key ] !== undefined ) {
+ ( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];
+ }
+ }
+ if ( deep ) {
+ jQuery.extend( true, target, deep );
+ }
+
+ return target;
+}
+
+/* Handles responses to an ajax request:
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+ var firstDataType, ct, finalDataType, type,
+ contents = s.contents,
+ dataTypes = s.dataTypes;
+
+ // Remove auto dataType and get content-type in the process
+ while ( dataTypes[ 0 ] === "*" ) {
+ dataTypes.shift();
+ if ( ct === undefined ) {
+ ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
+ }
+ }
+
+ // Check if we're dealing with a known content-type
+ if ( ct ) {
+ for ( type in contents ) {
+ if ( contents[ type ] && contents[ type ].test( ct ) ) {
+ dataTypes.unshift( type );
+ break;
+ }
+ }
+ }
+
+ // Check to see if we have a response for the expected dataType
+ if ( dataTypes[ 0 ] in responses ) {
+ finalDataType = dataTypes[ 0 ];
+ } else {
+ // Try convertible dataTypes
+ for ( type in responses ) {
+ if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+ finalDataType = type;
+ break;
+ }
+ if ( !firstDataType ) {
+ firstDataType = type;
+ }
+ }
+ // Or just use first one
+ finalDataType = finalDataType || firstDataType;
+ }
+
+ // If we found a dataType
+ // We add the dataType to the list if needed
+ // and return the corresponding response
+ if ( finalDataType ) {
+ if ( finalDataType !== dataTypes[ 0 ] ) {
+ dataTypes.unshift( finalDataType );
+ }
+ return responses[ finalDataType ];
+ }
+}
+
+/* Chain conversions given the request and the original response
+ * Also sets the responseXXX fields on the jqXHR instance
+ */
+function ajaxConvert( s, response, jqXHR, isSuccess ) {
+ var conv2, current, conv, tmp, prev,
+ converters = {},
+ // Work with a copy of dataTypes in case we need to modify it for conversion
+ dataTypes = s.dataTypes.slice();
+
+ // Create converters map with lowercased keys
+ if ( dataTypes[ 1 ] ) {
+ for ( conv in s.converters ) {
+ converters[ conv.toLowerCase() ] = s.converters[ conv ];
+ }
+ }
+
+ current = dataTypes.shift();
+
+ // Convert to each sequential dataType
+ while ( current ) {
+
+ if ( s.responseFields[ current ] ) {
+ jqXHR[ s.responseFields[ current ] ] = response;
+ }
+
+ // Apply the dataFilter if provided
+ if ( !prev && isSuccess && s.dataFilter ) {
+ response = s.dataFilter( response, s.dataType );
+ }
+
+ prev = current;
+ current = dataTypes.shift();
+
+ if ( current ) {
+
+ // There's only work to do if current dataType is non-auto
+ if ( current === "*" ) {
+
+ current = prev;
+
+ // Convert response if prev dataType is non-auto and differs from current
+ } else if ( prev !== "*" && prev !== current ) {
+
+ // Seek a direct converter
+ conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+ // If none found, seek a pair
+ if ( !conv ) {
+ for ( conv2 in converters ) {
+
+ // If conv2 outputs current
+ tmp = conv2.split( " " );
+ if ( tmp[ 1 ] === current ) {
+
+ // If prev can be converted to accepted input
+ conv = converters[ prev + " " + tmp[ 0 ] ] ||
+ converters[ "* " + tmp[ 0 ] ];
+ if ( conv ) {
+ // Condense equivalence converters
+ if ( conv === true ) {
+ conv = converters[ conv2 ];
+
+ // Otherwise, insert the intermediate dataType
+ } else if ( converters[ conv2 ] !== true ) {
+ current = tmp[ 0 ];
+ dataTypes.unshift( tmp[ 1 ] );
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ // Apply converter (if not an equivalence)
+ if ( conv !== true ) {
+
+ // Unless errors are allowed to bubble, catch and return them
+ if ( conv && s[ "throws" ] ) {
+ response = conv( response );
+ } else {
+ try {
+ response = conv( response );
+ } catch ( e ) {
+ return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return { state: "success", data: response };
+}
+
+jQuery.extend({
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+ etag: {},
+
+ ajaxSettings: {
+ url: ajaxLocation,
+ type: "GET",
+ isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+ global: true,
+ processData: true,
+ async: true,
+ contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+ /*
+ timeout: 0,
+ data: null,
+ dataType: null,
+ username: null,
+ password: null,
+ cache: null,
+ throws: false,
+ traditional: false,
+ headers: {},
+ */
+
+ accepts: {
+ "*": allTypes,
+ text: "text/plain",
+ html: "text/html",
+ xml: "application/xml, text/xml",
+ json: "application/json, text/javascript"
+ },
+
+ contents: {
+ xml: /xml/,
+ html: /html/,
+ json: /json/
+ },
+
+ responseFields: {
+ xml: "responseXML",
+ text: "responseText",
+ json: "responseJSON"
+ },
+
+ // Data converters
+ // Keys separate source (or catchall "*") and destination types with a single space
+ converters: {
+
+ // Convert anything to text
+ "* text": String,
+
+ // Text to html (true = no transformation)
+ "text html": true,
+
+ // Evaluate text as a json expression
+ "text json": jQuery.parseJSON,
+
+ // Parse text as xml
+ "text xml": jQuery.parseXML
+ },
+
+ // For options that shouldn't be deep extended:
+ // you can add your own custom options here if
+ // and when you create one that shouldn't be
+ // deep extended (see ajaxExtend)
+ flatOptions: {
+ url: true,
+ context: true
+ }
+ },
+
+ // Creates a full fledged settings object into target
+ // with both ajaxSettings and settings fields.
+ // If target is omitted, writes into ajaxSettings.
+ ajaxSetup: function( target, settings ) {
+ return settings ?
+
+ // Building a settings object
+ ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
+
+ // Extending ajaxSettings
+ ajaxExtend( jQuery.ajaxSettings, target );
+ },
+
+ ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+ ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+ // Main method
+ ajax: function( url, options ) {
+
+ // If url is an object, simulate pre-1.5 signature
+ if ( typeof url === "object" ) {
+ options = url;
+ url = undefined;
+ }
+
+ // Force options to be an object
+ options = options || {};
+
+ var // Cross-domain detection vars
+ parts,
+ // Loop variable
+ i,
+ // URL without anti-cache param
+ cacheURL,
+ // Response headers as string
+ responseHeadersString,
+ // timeout handle
+ timeoutTimer,
+
+ // To know if global events are to be dispatched
+ fireGlobals,
+
+ transport,
+ // Response headers
+ responseHeaders,
+ // Create the final options object
+ s = jQuery.ajaxSetup( {}, options ),
+ // Callbacks context
+ callbackContext = s.context || s,
+ // Context for global events is callbackContext if it is a DOM node or jQuery collection
+ globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
+ jQuery( callbackContext ) :
+ jQuery.event,
+ // Deferreds
+ deferred = jQuery.Deferred(),
+ completeDeferred = jQuery.Callbacks("once memory"),
+ // Status-dependent callbacks
+ statusCode = s.statusCode || {},
+ // Headers (they are sent all at once)
+ requestHeaders = {},
+ requestHeadersNames = {},
+ // The jqXHR state
+ state = 0,
+ // Default abort message
+ strAbort = "canceled",
+ // Fake xhr
+ jqXHR = {
+ readyState: 0,
+
+ // Builds headers hashtable if needed
+ getResponseHeader: function( key ) {
+ var match;
+ if ( state === 2 ) {
+ if ( !responseHeaders ) {
+ responseHeaders = {};
+ while ( (match = rheaders.exec( responseHeadersString )) ) {
+ responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+ }
+ }
+ match = responseHeaders[ key.toLowerCase() ];
+ }
+ return match == null ? null : match;
+ },
+
+ // Raw string
+ getAllResponseHeaders: function() {
+ return state === 2 ? responseHeadersString : null;
+ },
+
+ // Caches the header
+ setRequestHeader: function( name, value ) {
+ var lname = name.toLowerCase();
+ if ( !state ) {
+ name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+ requestHeaders[ name ] = value;
+ }
+ return this;
+ },
+
+ // Overrides response content-type header
+ overrideMimeType: function( type ) {
+ if ( !state ) {
+ s.mimeType = type;
+ }
+ return this;
+ },
+
+ // Status-dependent callbacks
+ statusCode: function( map ) {
+ var code;
+ if ( map ) {
+ if ( state < 2 ) {
+ for ( code in map ) {
+ // Lazy-add the new callback in a way that preserves old ones
+ statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
+ }
+ } else {
+ // Execute the appropriate callbacks
+ jqXHR.always( map[ jqXHR.status ] );
+ }
+ }
+ return this;
+ },
+
+ // Cancel the request
+ abort: function( statusText ) {
+ var finalText = statusText || strAbort;
+ if ( transport ) {
+ transport.abort( finalText );
+ }
+ done( 0, finalText );
+ return this;
+ }
+ };
+
+ // Attach deferreds
+ deferred.promise( jqXHR ).complete = completeDeferred.add;
+ jqXHR.success = jqXHR.done;
+ jqXHR.error = jqXHR.fail;
+
+ // Remove hash character (#7531: and string promotion)
+ // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+ // Handle falsy url in the settings object (#10093: consistency with old signature)
+ // We also use the url parameter if available
+ s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+ // Alias method option to type as per ticket #12004
+ s.type = options.method || options.type || s.method || s.type;
+
+ // Extract dataTypes list
+ s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ];
+
+ // A cross-domain request is in order when we have a protocol:host:port mismatch
+ if ( s.crossDomain == null ) {
+ parts = rurl.exec( s.url.toLowerCase() );
+ s.crossDomain = !!( parts &&
+ ( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
+ ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? "80" : "443" ) ) !==
+ ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? "80" : "443" ) ) )
+ );
+ }
+
+ // Convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
+
+ // Apply prefilters
+ inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+ // If request was aborted inside a prefilter, stop there
+ if ( state === 2 ) {
+ return jqXHR;
+ }
+
+ // We can fire global events as of now if asked to
+ // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
+ fireGlobals = jQuery.event && s.global;
+
+ // Watch for a new set of requests
+ if ( fireGlobals && jQuery.active++ === 0 ) {
+ jQuery.event.trigger("ajaxStart");
+ }
+
+ // Uppercase the type
+ s.type = s.type.toUpperCase();
+
+ // Determine if request has content
+ s.hasContent = !rnoContent.test( s.type );
+
+ // Save the URL in case we're toying with the If-Modified-Since
+ // and/or If-None-Match header later on
+ cacheURL = s.url;
+
+ // More options handling for requests with no content
+ if ( !s.hasContent ) {
+
+ // If data is available, append data to url
+ if ( s.data ) {
+ cacheURL = ( s.url += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
+ // #9682: remove data so that it's not used in an eventual retry
+ delete s.data;
+ }
+
+ // Add anti-cache in url if needed
+ if ( s.cache === false ) {
+ s.url = rts.test( cacheURL ) ?
+
+ // If there is already a '_' parameter, set its value
+ cacheURL.replace( rts, "$1_=" + nonce++ ) :
+
+ // Otherwise add one to the end
+ cacheURL + ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + nonce++;
+ }
+ }
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ if ( jQuery.lastModified[ cacheURL ] ) {
+ jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
+ }
+ if ( jQuery.etag[ cacheURL ] ) {
+ jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
+ }
+ }
+
+ // Set the correct header, if data is being sent
+ if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+ jqXHR.setRequestHeader( "Content-Type", s.contentType );
+ }
+
+ // Set the Accepts header for the server, depending on the dataType
+ jqXHR.setRequestHeader(
+ "Accept",
+ s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+ s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+ s.accepts[ "*" ]
+ );
+
+ // Check for headers option
+ for ( i in s.headers ) {
+ jqXHR.setRequestHeader( i, s.headers[ i ] );
+ }
+
+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+ // Abort if not done already and return
+ return jqXHR.abort();
+ }
+
+ // aborting is no longer a cancellation
+ strAbort = "abort";
+
+ // Install callbacks on deferreds
+ for ( i in { success: 1, error: 1, complete: 1 } ) {
+ jqXHR[ i ]( s[ i ] );
+ }
+
+ // Get transport
+ transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+ // If no transport, we auto-abort
+ if ( !transport ) {
+ done( -1, "No Transport" );
+ } else {
+ jqXHR.readyState = 1;
+
+ // Send global event
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+ }
+ // Timeout
+ if ( s.async && s.timeout > 0 ) {
+ timeoutTimer = setTimeout(function() {
+ jqXHR.abort("timeout");
+ }, s.timeout );
+ }
+
+ try {
+ state = 1;
+ transport.send( requestHeaders, done );
+ } catch ( e ) {
+ // Propagate exception as error if not done
+ if ( state < 2 ) {
+ done( -1, e );
+ // Simply rethrow otherwise
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ // Callback for when everything is done
+ function done( status, nativeStatusText, responses, headers ) {
+ var isSuccess, success, error, response, modified,
+ statusText = nativeStatusText;
+
+ // Called once
+ if ( state === 2 ) {
+ return;
+ }
+
+ // State is "done" now
+ state = 2;
+
+ // Clear timeout if it exists
+ if ( timeoutTimer ) {
+ clearTimeout( timeoutTimer );
+ }
+
+ // Dereference transport for early garbage collection
+ // (no matter how long the jqXHR object will be used)
+ transport = undefined;
+
+ // Cache response headers
+ responseHeadersString = headers || "";
+
+ // Set readyState
+ jqXHR.readyState = status > 0 ? 4 : 0;
+
+ // Determine if successful
+ isSuccess = status >= 200 && status < 300 || status === 304;
+
+ // Get response data
+ if ( responses ) {
+ response = ajaxHandleResponses( s, jqXHR, responses );
+ }
+
+ // Convert no matter what (that way responseXXX fields are always set)
+ response = ajaxConvert( s, response, jqXHR, isSuccess );
+
+ // If successful, handle type chaining
+ if ( isSuccess ) {
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ modified = jqXHR.getResponseHeader("Last-Modified");
+ if ( modified ) {
+ jQuery.lastModified[ cacheURL ] = modified;
+ }
+ modified = jqXHR.getResponseHeader("etag");
+ if ( modified ) {
+ jQuery.etag[ cacheURL ] = modified;
+ }
+ }
+
+ // if no content
+ if ( status === 204 || s.type === "HEAD" ) {
+ statusText = "nocontent";
+
+ // if not modified
+ } else if ( status === 304 ) {
+ statusText = "notmodified";
+
+ // If we have data, let's convert it
+ } else {
+ statusText = response.state;
+ success = response.data;
+ error = response.error;
+ isSuccess = !error;
+ }
+ } else {
+ // We extract error from statusText
+ // then normalize statusText and status for non-aborts
+ error = statusText;
+ if ( status || !statusText ) {
+ statusText = "error";
+ if ( status < 0 ) {
+ status = 0;
+ }
+ }
+ }
+
+ // Set data for the fake xhr object
+ jqXHR.status = status;
+ jqXHR.statusText = ( nativeStatusText || statusText ) + "";
+
+ // Success/Error
+ if ( isSuccess ) {
+ deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+ } else {
+ deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+ }
+
+ // Status-dependent callbacks
+ jqXHR.statusCode( statusCode );
+ statusCode = undefined;
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
+ [ jqXHR, s, isSuccess ? success : error ] );
+ }
+
+ // Complete
+ completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+ // Handle the global AJAX counter
+ if ( !( --jQuery.active ) ) {
+ jQuery.event.trigger("ajaxStop");
+ }
+ }
+ }
+
+ return jqXHR;
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get( url, data, callback, "json" );
+ },
+
+ getScript: function( url, callback ) {
+ return jQuery.get( url, undefined, callback, "script" );
+ }
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+ jQuery[ method ] = function( url, data, callback, type ) {
+ // shift arguments if data argument was omitted
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = undefined;
+ }
+
+ return jQuery.ajax({
+ url: url,
+ type: method,
+ dataType: type,
+ data: data,
+ success: callback
+ });
+ };
+});
+
+
+jQuery._evalUrl = function( url ) {
+ return jQuery.ajax({
+ url: url,
+ type: "GET",
+ dataType: "script",
+ async: false,
+ global: false,
+ "throws": true
+ });
+};
+
+
+jQuery.fn.extend({
+ wrapAll: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapAll( html.call(this, i) );
+ });
+ }
+
+ if ( this[0] ) {
+ // The elements to wrap the target around
+ var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+ if ( this[0].parentNode ) {
+ wrap.insertBefore( this[0] );
+ }
+
+ wrap.map(function() {
+ var elem = this;
+
+ while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+ elem = elem.firstChild;
+ }
+
+ return elem;
+ }).append( this );
+ }
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapInner( html.call(this, i) );
+ });
+ }
+
+ return this.each(function() {
+ var self = jQuery( this ),
+ contents = self.contents();
+
+ if ( contents.length ) {
+ contents.wrapAll( html );
+
+ } else {
+ self.append( html );
+ }
+ });
+ },
+
+ wrap: function( html ) {
+ var isFunction = jQuery.isFunction( html );
+
+ return this.each(function(i) {
+ jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+ });
+ },
+
+ unwrap: function() {
+ return this.parent().each(function() {
+ if ( !jQuery.nodeName( this, "body" ) ) {
+ jQuery( this ).replaceWith( this.childNodes );
+ }
+ }).end();
+ }
+});
+
+
+jQuery.expr.filters.hidden = function( elem ) {
+ // Support: Opera <= 12.12
+ // Opera reports offsetWidths and offsetHeights less than zero on some elements
+ return elem.offsetWidth <= 0 && elem.offsetHeight <= 0 ||
+ (!support.reliableHiddenOffsets() &&
+ ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none");
+};
+
+jQuery.expr.filters.visible = function( elem ) {
+ return !jQuery.expr.filters.hidden( elem );
+};
+
+
+
+
+var r20 = /%20/g,
+ rbracket = /\[\]$/,
+ rCRLF = /\r?\n/g,
+ rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
+ rsubmittable = /^(?:input|select|textarea|keygen)/i;
+
+function buildParams( prefix, obj, traditional, add ) {
+ var name;
+
+ if ( jQuery.isArray( obj ) ) {
+ // Serialize array item.
+ jQuery.each( obj, function( i, v ) {
+ if ( traditional || rbracket.test( prefix ) ) {
+ // Treat each array item as a scalar.
+ add( prefix, v );
+
+ } else {
+ // Item is non-scalar (array or object), encode its numeric index.
+ buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
+ }
+ });
+
+ } else if ( !traditional && jQuery.type( obj ) === "object" ) {
+ // Serialize object item.
+ for ( name in obj ) {
+ buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+ }
+
+ } else {
+ // Serialize scalar item.
+ add( prefix, obj );
+ }
+}
+
+// Serialize an array of form elements or a set of
+// key/values into a query string
+jQuery.param = function( a, traditional ) {
+ var prefix,
+ s = [],
+ add = function( key, value ) {
+ // If value is a function, invoke it and return its value
+ value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
+ s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+ };
+
+ // Set traditional to true for jQuery <= 1.3.2 behavior.
+ if ( traditional === undefined ) {
+ traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
+ }
+
+ // If an array was passed in, assume that it is an array of form elements.
+ if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+ // Serialize the form elements
+ jQuery.each( a, function() {
+ add( this.name, this.value );
+ });
+
+ } else {
+ // If traditional, encode the "old" way (the way 1.3.2 or older
+ // did it), otherwise encode params recursively.
+ for ( prefix in a ) {
+ buildParams( prefix, a[ prefix ], traditional, add );
+ }
+ }
+
+ // Return the resulting serialization
+ return s.join( "&" ).replace( r20, "+" );
+};
+
+jQuery.fn.extend({
+ serialize: function() {
+ return jQuery.param( this.serializeArray() );
+ },
+ serializeArray: function() {
+ return this.map(function() {
+ // Can add propHook for "elements" to filter or add form elements
+ var elements = jQuery.prop( this, "elements" );
+ return elements ? jQuery.makeArray( elements ) : this;
+ })
+ .filter(function() {
+ var type = this.type;
+ // Use .is(":disabled") so that fieldset[disabled] works
+ return this.name && !jQuery( this ).is( ":disabled" ) &&
+ rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
+ ( this.checked || !rcheckableType.test( type ) );
+ })
+ .map(function( i, elem ) {
+ var val = jQuery( this ).val();
+
+ return val == null ?
+ null :
+ jQuery.isArray( val ) ?
+ jQuery.map( val, function( val ) {
+ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }) :
+ { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }).get();
+ }
+});
+
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject !== undefined ?
+ // Support: IE6+
+ function() {
+
+ // XHR cannot access local files, always use ActiveX for that case
+ return !this.isLocal &&
+
+ // Support: IE7-8
+ // oldIE XHR does not support non-RFC2616 methods (#13240)
+ // See http://msdn.microsoft.com/en-us/library/ie/ms536648(v=vs.85).aspx
+ // and http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9
+ // Although this check for six methods instead of eight
+ // since IE also does not support "trace" and "connect"
+ /^(get|post|head|put|delete|options)$/i.test( this.type ) &&
+
+ createStandardXHR() || createActiveXHR();
+ } :
+ // For all other browsers, use the standard XMLHttpRequest object
+ createStandardXHR;
+
+var xhrId = 0,
+ xhrCallbacks = {},
+ xhrSupported = jQuery.ajaxSettings.xhr();
+
+// Support: IE<10
+// Open requests must be manually aborted on unload (#5280)
+// See https://support.microsoft.com/kb/2856746 for more info
+if ( window.attachEvent ) {
+ window.attachEvent( "onunload", function() {
+ for ( var key in xhrCallbacks ) {
+ xhrCallbacks[ key ]( undefined, true );
+ }
+ });
+}
+
+// Determine support properties
+support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
+xhrSupported = support.ajax = !!xhrSupported;
+
+// Create transport if the browser can provide an xhr
+if ( xhrSupported ) {
+
+ jQuery.ajaxTransport(function( options ) {
+ // Cross domain only allowed if supported through XMLHttpRequest
+ if ( !options.crossDomain || support.cors ) {
+
+ var callback;
+
+ return {
+ send: function( headers, complete ) {
+ var i,
+ xhr = options.xhr(),
+ id = ++xhrId;
+
+ // Open the socket
+ xhr.open( options.type, options.url, options.async, options.username, options.password );
+
+ // Apply custom fields if provided
+ if ( options.xhrFields ) {
+ for ( i in options.xhrFields ) {
+ xhr[ i ] = options.xhrFields[ i ];
+ }
+ }
+
+ // Override mime type if needed
+ if ( options.mimeType && xhr.overrideMimeType ) {
+ xhr.overrideMimeType( options.mimeType );
+ }
+
+ // X-Requested-With header
+ // For cross-domain requests, seeing as conditions for a preflight are
+ // akin to a jigsaw puzzle, we simply never set it to be sure.
+ // (it can always be set on a per-request basis or even using ajaxSetup)
+ // For same-domain requests, won't change header if already provided.
+ if ( !options.crossDomain && !headers["X-Requested-With"] ) {
+ headers["X-Requested-With"] = "XMLHttpRequest";
+ }
+
+ // Set headers
+ for ( i in headers ) {
+ // Support: IE<9
+ // IE's ActiveXObject throws a 'Type Mismatch' exception when setting
+ // request header to a null-value.
+ //
+ // To keep consistent with other XHR implementations, cast the value
+ // to string and ignore `undefined`.
+ if ( headers[ i ] !== undefined ) {
+ xhr.setRequestHeader( i, headers[ i ] + "" );
+ }
+ }
+
+ // Do send the request
+ // This may raise an exception which is actually
+ // handled in jQuery.ajax (so no try/catch here)
+ xhr.send( ( options.hasContent && options.data ) || null );
+
+ // Listener
+ callback = function( _, isAbort ) {
+ var status, statusText, responses;
+
+ // Was never called and is aborted or complete
+ if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+ // Clean up
+ delete xhrCallbacks[ id ];
+ callback = undefined;
+ xhr.onreadystatechange = jQuery.noop;
+
+ // Abort manually if needed
+ if ( isAbort ) {
+ if ( xhr.readyState !== 4 ) {
+ xhr.abort();
+ }
+ } else {
+ responses = {};
+ status = xhr.status;
+
+ // Support: IE<10
+ // Accessing binary-data responseText throws an exception
+ // (#11426)
+ if ( typeof xhr.responseText === "string" ) {
+ responses.text = xhr.responseText;
+ }
+
+ // Firefox throws an exception when accessing
+ // statusText for faulty cross-domain requests
+ try {
+ statusText = xhr.statusText;
+ } catch( e ) {
+ // We normalize with Webkit giving an empty statusText
+ statusText = "";
+ }
+
+ // Filter status for non standard behaviors
+
+ // If the request is local and we have data: assume a success
+ // (success with no data won't get notified, that's the best we
+ // can do given current implementations)
+ if ( !status && options.isLocal && !options.crossDomain ) {
+ status = responses.text ? 200 : 404;
+ // IE - #1450: sometimes returns 1223 when it should be 204
+ } else if ( status === 1223 ) {
+ status = 204;
+ }
+ }
+ }
+
+ // Call complete if needed
+ if ( responses ) {
+ complete( status, statusText, responses, xhr.getAllResponseHeaders() );
+ }
+ };
+
+ if ( !options.async ) {
+ // if we're in sync mode we fire the callback
+ callback();
+ } else if ( xhr.readyState === 4 ) {
+ // (IE6 & IE7) if it's in cache and has been
+ // retrieved directly we need to fire the callback
+ setTimeout( callback );
+ } else {
+ // Add to the list of active xhr callbacks
+ xhr.onreadystatechange = xhrCallbacks[ id ] = callback;
+ }
+ },
+
+ abort: function() {
+ if ( callback ) {
+ callback( undefined, true );
+ }
+ }
+ };
+ }
+ });
+}
+
+// Functions to create xhrs
+function createStandardXHR() {
+ try {
+ return new window.XMLHttpRequest();
+ } catch( e ) {}
+}
+
+function createActiveXHR() {
+ try {
+ return new window.ActiveXObject( "Microsoft.XMLHTTP" );
+ } catch( e ) {}
+}
+
+
+
+
+// Install script dataType
+jQuery.ajaxSetup({
+ accepts: {
+ script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+ },
+ contents: {
+ script: /(?:java|ecma)script/
+ },
+ converters: {
+ "text script": function( text ) {
+ jQuery.globalEval( text );
+ return text;
+ }
+ }
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+ if ( s.cache === undefined ) {
+ s.cache = false;
+ }
+ if ( s.crossDomain ) {
+ s.type = "GET";
+ s.global = false;
+ }
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+ // This transport only deals with cross domain requests
+ if ( s.crossDomain ) {
+
+ var script,
+ head = document.head || jQuery("head")[0] || document.documentElement;
+
+ return {
+
+ send: function( _, callback ) {
+
+ script = document.createElement("script");
+
+ script.async = true;
+
+ if ( s.scriptCharset ) {
+ script.charset = s.scriptCharset;
+ }
+
+ script.src = s.url;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+ if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
+
+ // Remove the script
+ if ( script.parentNode ) {
+ script.parentNode.removeChild( script );
+ }
+
+ // Dereference the script
+ script = null;
+
+ // Callback if not abort
+ if ( !isAbort ) {
+ callback( 200, "success" );
+ }
+ }
+ };
+
+ // Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending
+ // Use native DOM manipulation to avoid our domManip AJAX trickery
+ head.insertBefore( script, head.firstChild );
+ },
+
+ abort: function() {
+ if ( script ) {
+ script.onload( undefined, true );
+ }
+ }
+ };
+ }
+});
+
+
+
+
+var oldCallbacks = [],
+ rjsonp = /(=)\?(?=&|$)|\?\?/;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+ jsonp: "callback",
+ jsonpCallback: function() {
+ var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
+ this[ callback ] = true;
+ return callback;
+ }
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+ var callbackName, overwritten, responseContainer,
+ jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
+ "url" :
+ typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
+ );
+
+ // Handle iff the expected data type is "jsonp" or we have a parameter to set
+ if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
+
+ // Get callback name, remembering preexisting value associated with it
+ callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
+ s.jsonpCallback() :
+ s.jsonpCallback;
+
+ // Insert callback into url or form data
+ if ( jsonProp ) {
+ s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
+ } else if ( s.jsonp !== false ) {
+ s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
+ }
+
+ // Use data converter to retrieve json after script execution
+ s.converters["script json"] = function() {
+ if ( !responseContainer ) {
+ jQuery.error( callbackName + " was not called" );
+ }
+ return responseContainer[ 0 ];
+ };
+
+ // force json dataType
+ s.dataTypes[ 0 ] = "json";
+
+ // Install callback
+ overwritten = window[ callbackName ];
+ window[ callbackName ] = function() {
+ responseContainer = arguments;
+ };
+
+ // Clean-up function (fires after converters)
+ jqXHR.always(function() {
+ // Restore preexisting value
+ window[ callbackName ] = overwritten;
+
+ // Save back as free
+ if ( s[ callbackName ] ) {
+ // make sure that re-using the options doesn't screw things around
+ s.jsonpCallback = originalSettings.jsonpCallback;
+
+ // save the callback name for future use
+ oldCallbacks.push( callbackName );
+ }
+
+ // Call if it was a function and we have a response
+ if ( responseContainer && jQuery.isFunction( overwritten ) ) {
+ overwritten( responseContainer[ 0 ] );
+ }
+
+ responseContainer = overwritten = undefined;
+ });
+
+ // Delegate to script
+ return "script";
+ }
+});
+
+
+
+
+// data: string of html
+// context (optional): If specified, the fragment will be created in this context, defaults to document
+// keepScripts (optional): If true, will include scripts passed in the html string
+jQuery.parseHTML = function( data, context, keepScripts ) {
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+ if ( typeof context === "boolean" ) {
+ keepScripts = context;
+ context = false;
+ }
+ context = context || document;
+
+ var parsed = rsingleTag.exec( data ),
+ scripts = !keepScripts && [];
+
+ // Single tag
+ if ( parsed ) {
+ return [ context.createElement( parsed[1] ) ];
+ }
+
+ parsed = jQuery.buildFragment( [ data ], context, scripts );
+
+ if ( scripts && scripts.length ) {
+ jQuery( scripts ).remove();
+ }
+
+ return jQuery.merge( [], parsed.childNodes );
+};
+
+
+// Keep a copy of the old load method
+var _load = jQuery.fn.load;
+
+/**
+ * Load a url into a page
+ */
+jQuery.fn.load = function( url, params, callback ) {
+ if ( typeof url !== "string" && _load ) {
+ return _load.apply( this, arguments );
+ }
+
+ var selector, response, type,
+ self = this,
+ off = url.indexOf(" ");
+
+ if ( off >= 0 ) {
+ selector = jQuery.trim( url.slice( off, url.length ) );
+ url = url.slice( 0, off );
+ }
+
+ // If it's a function
+ if ( jQuery.isFunction( params ) ) {
+
+ // We assume that it's the callback
+ callback = params;
+ params = undefined;
+
+ // Otherwise, build a param string
+ } else if ( params && typeof params === "object" ) {
+ type = "POST";
+ }
+
+ // If we have elements to modify, make the request
+ if ( self.length > 0 ) {
+ jQuery.ajax({
+ url: url,
+
+ // if "type" variable is undefined, then "GET" method will be used
+ type: type,
+ dataType: "html",
+ data: params
+ }).done(function( responseText ) {
+
+ // Save response for use in complete callback
+ response = arguments;
+
+ self.html( selector ?
+
+ // If a selector was specified, locate the right elements in a dummy div
+ // Exclude scripts to avoid IE 'Permission Denied' errors
+ jQuery("").append( jQuery.parseHTML( responseText ) ).find( selector ) :
+
+ // Otherwise use the full result
+ responseText );
+
+ }).complete( callback && function( jqXHR, status ) {
+ self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
+ });
+ }
+
+ return this;
+};
+
+
+
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ) {
+ jQuery.fn[ type ] = function( fn ) {
+ return this.on( type, fn );
+ };
+});
+
+
+
+
+jQuery.expr.filters.animated = function( elem ) {
+ return jQuery.grep(jQuery.timers, function( fn ) {
+ return elem === fn.elem;
+ }).length;
+};
+
+
+
+
+
+var docElem = window.document.documentElement;
+
+/**
+ * Gets a window from an element
+ */
+function getWindow( elem ) {
+ return jQuery.isWindow( elem ) ?
+ elem :
+ elem.nodeType === 9 ?
+ elem.defaultView || elem.parentWindow :
+ false;
+}
+
+jQuery.offset = {
+ setOffset: function( elem, options, i ) {
+ var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
+ position = jQuery.css( elem, "position" ),
+ curElem = jQuery( elem ),
+ props = {};
+
+ // set position first, in-case top/left are set even on static elem
+ if ( position === "static" ) {
+ elem.style.position = "relative";
+ }
+
+ curOffset = curElem.offset();
+ curCSSTop = jQuery.css( elem, "top" );
+ curCSSLeft = jQuery.css( elem, "left" );
+ calculatePosition = ( position === "absolute" || position === "fixed" ) &&
+ jQuery.inArray("auto", [ curCSSTop, curCSSLeft ] ) > -1;
+
+ // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+ if ( calculatePosition ) {
+ curPosition = curElem.position();
+ curTop = curPosition.top;
+ curLeft = curPosition.left;
+ } else {
+ curTop = parseFloat( curCSSTop ) || 0;
+ curLeft = parseFloat( curCSSLeft ) || 0;
+ }
+
+ if ( jQuery.isFunction( options ) ) {
+ options = options.call( elem, i, curOffset );
+ }
+
+ if ( options.top != null ) {
+ props.top = ( options.top - curOffset.top ) + curTop;
+ }
+ if ( options.left != null ) {
+ props.left = ( options.left - curOffset.left ) + curLeft;
+ }
+
+ if ( "using" in options ) {
+ options.using.call( elem, props );
+ } else {
+ curElem.css( props );
+ }
+ }
+};
+
+jQuery.fn.extend({
+ offset: function( options ) {
+ if ( arguments.length ) {
+ return options === undefined ?
+ this :
+ this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ var docElem, win,
+ box = { top: 0, left: 0 },
+ elem = this[ 0 ],
+ doc = elem && elem.ownerDocument;
+
+ if ( !doc ) {
+ return;
+ }
+
+ docElem = doc.documentElement;
+
+ // Make sure it's not a disconnected DOM node
+ if ( !jQuery.contains( docElem, elem ) ) {
+ return box;
+ }
+
+ // If we don't have gBCR, just use 0,0 rather than error
+ // BlackBerry 5, iOS 3 (original iPhone)
+ if ( typeof elem.getBoundingClientRect !== strundefined ) {
+ box = elem.getBoundingClientRect();
+ }
+ win = getWindow( doc );
+ return {
+ top: box.top + ( win.pageYOffset || docElem.scrollTop ) - ( docElem.clientTop || 0 ),
+ left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )
+ };
+ },
+
+ position: function() {
+ if ( !this[ 0 ] ) {
+ return;
+ }
+
+ var offsetParent, offset,
+ parentOffset = { top: 0, left: 0 },
+ elem = this[ 0 ];
+
+ // fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is its only offset parent
+ if ( jQuery.css( elem, "position" ) === "fixed" ) {
+ // we assume that getBoundingClientRect is available when computed position is fixed
+ offset = elem.getBoundingClientRect();
+ } else {
+ // Get *real* offsetParent
+ offsetParent = this.offsetParent();
+
+ // Get correct offsets
+ offset = this.offset();
+ if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
+ parentOffset = offsetParent.offset();
+ }
+
+ // Add offsetParent borders
+ parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
+ parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
+ }
+
+ // Subtract parent offsets and element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ return {
+ top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
+ left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true)
+ };
+ },
+
+ offsetParent: function() {
+ return this.map(function() {
+ var offsetParent = this.offsetParent || docElem;
+
+ while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position" ) === "static" ) ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent || docElem;
+ });
+ }
+});
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
+ var top = /Y/.test( prop );
+
+ jQuery.fn[ method ] = function( val ) {
+ return access( this, function( elem, method, val ) {
+ var win = getWindow( elem );
+
+ if ( val === undefined ) {
+ return win ? (prop in win) ? win[ prop ] :
+ win.document.documentElement[ method ] :
+ elem[ method ];
+ }
+
+ if ( win ) {
+ win.scrollTo(
+ !top ? val : jQuery( win ).scrollLeft(),
+ top ? val : jQuery( win ).scrollTop()
+ );
+
+ } else {
+ elem[ method ] = val;
+ }
+ }, method, val, arguments.length, null );
+ };
+});
+
+// Add the top/left cssHooks using jQuery.fn.position
+// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+// getComputedStyle returns percent when specified for top/left/bottom/right
+// rather than make the css module depend on the offset module, we just check for it here
+jQuery.each( [ "top", "left" ], function( i, prop ) {
+ jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
+ function( elem, computed ) {
+ if ( computed ) {
+ computed = curCSS( elem, prop );
+ // if curCSS returns percentage, fallback to offset
+ return rnumnonpx.test( computed ) ?
+ jQuery( elem ).position()[ prop ] + "px" :
+ computed;
+ }
+ }
+ );
+});
+
+
+// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+ jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
+ // margin is only for outerHeight, outerWidth
+ jQuery.fn[ funcName ] = function( margin, value ) {
+ var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+ extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+ return access( this, function( elem, type, value ) {
+ var doc;
+
+ if ( jQuery.isWindow( elem ) ) {
+ // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
+ // isn't a whole lot we can do. See pull request at this URL for discussion:
+ // https://github.com/jquery/jquery/pull/764
+ return elem.document.documentElement[ "client" + name ];
+ }
+
+ // Get document width or height
+ if ( elem.nodeType === 9 ) {
+ doc = elem.documentElement;
+
+ // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
+ // unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
+ return Math.max(
+ elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+ elem.body[ "offset" + name ], doc[ "offset" + name ],
+ doc[ "client" + name ]
+ );
+ }
+
+ return value === undefined ?
+ // Get width or height on the element, requesting but not forcing parseFloat
+ jQuery.css( elem, type, extra ) :
+
+ // Set width or height on the element
+ jQuery.style( elem, type, value, extra );
+ }, type, chainable ? margin : undefined, chainable, null );
+ };
+ });
+});
+
+
+// The number of elements contained in the matched element set
+jQuery.fn.size = function() {
+ return this.length;
+};
+
+jQuery.fn.andSelf = jQuery.fn.addBack;
+
+
+
+
+// Register as a named AMD module, since jQuery can be concatenated with other
+// files that may use define, but not via a proper concatenation script that
+// understands anonymous AMD modules. A named AMD is safest and most robust
+// way to register. Lowercase jquery is used because AMD module names are
+// derived from file names, and jQuery is normally delivered in a lowercase
+// file name. Do this after creating the global so that if an AMD module wants
+// to call noConflict to hide this version of jQuery, it will work.
+
+// Note that for maximum portability, libraries that are not jQuery should
+// declare themselves as anonymous modules, and avoid setting a global if an
+// AMD loader is present. jQuery is a special case. For more information, see
+// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
+
+if ( typeof define === "function" && define.amd ) {
+ define( "jquery", [], function() {
+ return jQuery;
+ });
+}
+
+
+
+
+var
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$;
+
+jQuery.noConflict = function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+};
+
+// Expose jQuery and $ identifiers, even in
+// AMD (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
+// and CommonJS for browser emulators (#13566)
+if ( typeof noGlobal === strundefined ) {
+ window.jQuery = window.$ = jQuery;
+}
+
+
+
+
+return jQuery;
+
+}));
diff --git a/Public/libs/jquery/1.x/jquery.min.js b/Public/libs/jquery/1.x/jquery.min.js
new file mode 100644
index 00000000..0f60b7bd
--- /dev/null
+++ b/Public/libs/jquery/1.x/jquery.min.js
@@ -0,0 +1,5 @@
+/*! jQuery v1.11.3 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */
+!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.3",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b="length"in a&&a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="
",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++d
b;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML=" ","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML=" ",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;
+
+return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML=" a ",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML=" ",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function aa(){return!0}function ba(){return!1}function ca(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h ]","i"),ha=/^\s+/,ia=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,ja=/<([\w:]+)/,ka=/\s*$/g,ra={option:[1,""," "],legend:[1,""," "],area:[1,""," "],param:[1,""," "],thead:[1,""],tr:[2,""],col:[2,""],td:[3,""],_default:k.htmlSerialize?[0,"",""]:[1,"X","
"]},sa=da(y),ta=sa.appendChild(y.createElement("div"));ra.optgroup=ra.option,ra.tbody=ra.tfoot=ra.colgroup=ra.caption=ra.thead,ra.th=ra.td;function ua(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ua(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function va(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wa(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xa(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function ya(a){var b=pa.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function za(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Aa(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Ba(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xa(b).text=a.text,ya(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!ga.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ta.innerHTML=a.outerHTML,ta.removeChild(f=ta.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ua(f),h=ua(a),g=0;null!=(e=h[g]);++g)d[g]&&Ba(e,d[g]);if(b)if(c)for(h=h||ua(a),d=d||ua(f),g=0;null!=(e=h[g]);g++)Aa(e,d[g]);else Aa(a,f);return d=ua(f,"script"),d.length>0&&za(d,!i&&ua(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=da(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(la.test(f)){h=h||o.appendChild(b.createElement("div")),i=(ja.exec(f)||["",""])[1].toLowerCase(),l=ra[i]||ra._default,h.innerHTML=l[1]+f.replace(ia,"<$1>$2>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&ha.test(f)&&p.push(b.createTextNode(ha.exec(f)[0])),!k.tbody){f="table"!==i||ka.test(f)?""!==l[1]||ka.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ua(p,"input"),va),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ua(o.appendChild(f),"script"),g&&za(h),c)){e=0;while(f=h[e++])oa.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wa(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ua(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&za(ua(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ua(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fa,""):void 0;if(!("string"!=typeof a||ma.test(a)||!k.htmlSerialize&&ga.test(a)||!k.leadingWhitespace&&ha.test(a)||ra[(ja.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ia,"<$1>$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ua(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ua(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&na.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ua(i,"script"),xa),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ua(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,ya),j=0;f>j;j++)d=g[j],oa.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qa,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Ca,Da={};function Ea(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fa(a){var b=y,c=Da[a];return c||(c=Ea(a,b),"none"!==c&&c||(Ca=(Ca||m("")).appendTo(b.documentElement),b=(Ca[0].contentWindow||Ca[0].contentDocument).document,b.write(),b.close(),c=Ea(a,b),Ca.detach()),Da[a]=c),c}!function(){var a;k.shrinkWrapBlocks=function(){if(null!=a)return a;a=!1;var b,c,d;return c=y.getElementsByTagName("body")[0],c&&c.style?(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:1px;width:1px;zoom:1",b.appendChild(y.createElement("div")).style.width="5px",a=3!==b.offsetWidth),c.removeChild(d),a):void 0}}();var Ga=/^margin/,Ha=new RegExp("^("+S+")(?!px)[a-z%]+$","i"),Ia,Ja,Ka=/^(top|right|bottom|left)$/;a.getComputedStyle?(Ia=function(b){return b.ownerDocument.defaultView.opener?b.ownerDocument.defaultView.getComputedStyle(b,null):a.getComputedStyle(b,null)},Ja=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ia(a),g=c?c.getPropertyValue(b)||c[b]:void 0,c&&(""!==g||m.contains(a.ownerDocument,a)||(g=m.style(a,b)),Ha.test(g)&&Ga.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0===g?g:g+""}):y.documentElement.currentStyle&&(Ia=function(a){return a.currentStyle},Ja=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ia(a),g=c?c[b]:void 0,null==g&&h&&h[b]&&(g=h[b]),Ha.test(g)&&!Ka.test(b)&&(d=h.left,e=a.runtimeStyle,f=e&&e.left,f&&(e.left=a.currentStyle.left),h.left="fontSize"===b?"1em":g,g=h.pixelLeft+"px",h.left=d,f&&(e.left=f)),void 0===g?g:g+""||"auto"});function La(a,b){return{get:function(){var c=a();if(null!=c)return c?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d,e,f,g,h;if(b=y.createElement("div"),b.innerHTML=" a ",d=b.getElementsByTagName("a")[0],c=d&&d.style){c.cssText="float:left;opacity:.5",k.opacity="0.5"===c.opacity,k.cssFloat=!!c.cssFloat,b.style.backgroundClip="content-box",b.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===b.style.backgroundClip,k.boxSizing=""===c.boxSizing||""===c.MozBoxSizing||""===c.WebkitBoxSizing,m.extend(k,{reliableHiddenOffsets:function(){return null==g&&i(),g},boxSizingReliable:function(){return null==f&&i(),f},pixelPosition:function(){return null==e&&i(),e},reliableMarginRight:function(){return null==h&&i(),h}});function i(){var b,c,d,i;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),b.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",e=f=!1,h=!0,a.getComputedStyle&&(e="1%"!==(a.getComputedStyle(b,null)||{}).top,f="4px"===(a.getComputedStyle(b,null)||{width:"4px"}).width,i=b.appendChild(y.createElement("div")),i.style.cssText=b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",i.style.marginRight=i.style.width="0",b.style.width="1px",h=!parseFloat((a.getComputedStyle(i,null)||{}).marginRight),b.removeChild(i)),b.innerHTML="",i=b.getElementsByTagName("td"),i[0].style.cssText="margin:0;border:0;padding:0;display:none",g=0===i[0].offsetHeight,g&&(i[0].style.display="",i[1].style.display="none",g=0===i[0].offsetHeight),c.removeChild(d))}}}(),m.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var Ma=/alpha\([^)]*\)/i,Na=/opacity\s*=\s*([^)]*)/,Oa=/^(none|table(?!-c[ea]).+)/,Pa=new RegExp("^("+S+")(.*)$","i"),Qa=new RegExp("^([+-])=("+S+")","i"),Ra={position:"absolute",visibility:"hidden",display:"block"},Sa={letterSpacing:"0",fontWeight:"400"},Ta=["Webkit","O","Moz","ms"];function Ua(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=Ta.length;while(e--)if(b=Ta[e]+c,b in a)return b;return d}function Va(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=m._data(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&U(d)&&(f[g]=m._data(d,"olddisplay",Fa(d.nodeName)))):(e=U(d),(c&&"none"!==c||!e)&&m._data(d,"olddisplay",e?c:m.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}function Wa(a,b,c){var d=Pa.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Xa(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=m.css(a,c+T[f],!0,e)),d?("content"===c&&(g-=m.css(a,"padding"+T[f],!0,e)),"margin"!==c&&(g-=m.css(a,"border"+T[f]+"Width",!0,e))):(g+=m.css(a,"padding"+T[f],!0,e),"padding"!==c&&(g+=m.css(a,"border"+T[f]+"Width",!0,e)));return g}function Ya(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=Ia(a),g=k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=Ja(a,b,f),(0>e||null==e)&&(e=a.style[b]),Ha.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Xa(a,b,c||(g?"border":"content"),d,f)+"px"}m.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Ja(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":k.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=m.camelCase(b),i=a.style;if(b=m.cssProps[h]||(m.cssProps[h]=Ua(i,h)),g=m.cssHooks[b]||m.cssHooks[h],void 0===c)return g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b];if(f=typeof c,"string"===f&&(e=Qa.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(m.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||m.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),!(g&&"set"in g&&void 0===(c=g.set(a,c,d)))))try{i[b]=c}catch(j){}}},css:function(a,b,c,d){var e,f,g,h=m.camelCase(b);return b=m.cssProps[h]||(m.cssProps[h]=Ua(a.style,h)),g=m.cssHooks[b]||m.cssHooks[h],g&&"get"in g&&(f=g.get(a,!0,c)),void 0===f&&(f=Ja(a,b,d)),"normal"===f&&b in Sa&&(f=Sa[b]),""===c||c?(e=parseFloat(f),c===!0||m.isNumeric(e)?e||0:f):f}}),m.each(["height","width"],function(a,b){m.cssHooks[b]={get:function(a,c,d){return c?Oa.test(m.css(a,"display"))&&0===a.offsetWidth?m.swap(a,Ra,function(){return Ya(a,b,d)}):Ya(a,b,d):void 0},set:function(a,c,d){var e=d&&Ia(a);return Wa(a,c,d?Xa(a,b,d,k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,e),e):0)}}}),k.opacity||(m.cssHooks.opacity={get:function(a,b){return Na.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=m.isNumeric(b)?"alpha(opacity="+100*b+")":"",f=d&&d.filter||c.filter||"";c.zoom=1,(b>=1||""===b)&&""===m.trim(f.replace(Ma,""))&&c.removeAttribute&&(c.removeAttribute("filter"),""===b||d&&!d.filter)||(c.filter=Ma.test(f)?f.replace(Ma,e):f+" "+e)}}),m.cssHooks.marginRight=La(k.reliableMarginRight,function(a,b){return b?m.swap(a,{display:"inline-block"},Ja,[a,"marginRight"]):void 0}),m.each({margin:"",padding:"",border:"Width"},function(a,b){m.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+T[d]+b]=f[d]||f[d-2]||f[0];return e}},Ga.test(a)||(m.cssHooks[a+b].set=Wa)}),m.fn.extend({css:function(a,b){return V(this,function(a,b,c){var d,e,f={},g=0;if(m.isArray(b)){for(d=Ia(a),e=b.length;e>g;g++)f[b[g]]=m.css(a,b[g],!1,d);return f}return void 0!==c?m.style(a,b,c):m.css(a,b)},a,b,arguments.length>1)},show:function(){return Va(this,!0)},hide:function(){return Va(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){U(this)?m(this).show():m(this).hide()})}});function Za(a,b,c,d,e){
+return new Za.prototype.init(a,b,c,d,e)}m.Tween=Za,Za.prototype={constructor:Za,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(m.cssNumber[c]?"":"px")},cur:function(){var a=Za.propHooks[this.prop];return a&&a.get?a.get(this):Za.propHooks._default.get(this)},run:function(a){var b,c=Za.propHooks[this.prop];return this.options.duration?this.pos=b=m.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Za.propHooks._default.set(this),this}},Za.prototype.init.prototype=Za.prototype,Za.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=m.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){m.fx.step[a.prop]?m.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[m.cssProps[a.prop]]||m.cssHooks[a.prop])?m.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Za.propHooks.scrollTop=Za.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},m.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},m.fx=Za.prototype.init,m.fx.step={};var $a,_a,ab=/^(?:toggle|show|hide)$/,bb=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),cb=/queueHooks$/,db=[ib],eb={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=bb.exec(b),f=e&&e[3]||(m.cssNumber[a]?"":"px"),g=(m.cssNumber[a]||"px"!==f&&+d)&&bb.exec(m.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,m.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function fb(){return setTimeout(function(){$a=void 0}),$a=m.now()}function gb(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=T[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function hb(a,b,c){for(var d,e=(eb[b]||[]).concat(eb["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ib(a,b,c){var d,e,f,g,h,i,j,l,n=this,o={},p=a.style,q=a.nodeType&&U(a),r=m._data(a,"fxshow");c.queue||(h=m._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,n.always(function(){n.always(function(){h.unqueued--,m.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=m.css(a,"display"),l="none"===j?m._data(a,"olddisplay")||Fa(a.nodeName):j,"inline"===l&&"none"===m.css(a,"float")&&(k.inlineBlockNeedsLayout&&"inline"!==Fa(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",k.shrinkWrapBlocks()||n.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],ab.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||m.style(a,d)}else j=void 0;if(m.isEmptyObject(o))"inline"===("none"===j?Fa(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=m._data(a,"fxshow",{}),f&&(r.hidden=!q),q?m(a).show():n.done(function(){m(a).hide()}),n.done(function(){var b;m._removeData(a,"fxshow");for(b in o)m.style(a,b,o[b])});for(d in o)g=hb(q?r[d]:0,d,n),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function jb(a,b){var c,d,e,f,g;for(c in a)if(d=m.camelCase(c),e=b[d],f=a[c],m.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=m.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kb(a,b,c){var d,e,f=0,g=db.length,h=m.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=$a||fb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:m.extend({},b),opts:m.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:$a||fb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=m.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jb(k,j.opts.specialEasing);g>f;f++)if(d=db[f].call(j,a,k,j.opts))return d;return m.map(k,hb,j),m.isFunction(j.opts.start)&&j.opts.start.call(a,j),m.fx.timer(m.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}m.Animation=m.extend(kb,{tweener:function(a,b){m.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],eb[c]=eb[c]||[],eb[c].unshift(b)},prefilter:function(a,b){b?db.unshift(a):db.push(a)}}),m.speed=function(a,b,c){var d=a&&"object"==typeof a?m.extend({},a):{complete:c||!c&&b||m.isFunction(a)&&a,duration:a,easing:c&&b||b&&!m.isFunction(b)&&b};return d.duration=m.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in m.fx.speeds?m.fx.speeds[d.duration]:m.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){m.isFunction(d.old)&&d.old.call(this),d.queue&&m.dequeue(this,d.queue)},d},m.fn.extend({fadeTo:function(a,b,c,d){return this.filter(U).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=m.isEmptyObject(a),f=m.speed(b,c,d),g=function(){var b=kb(this,m.extend({},a),f);(e||m._data(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=m.timers,g=m._data(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&cb.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&m.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=m._data(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=m.timers,g=d?d.length:0;for(c.finish=!0,m.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),m.each(["toggle","show","hide"],function(a,b){var c=m.fn[b];m.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(gb(b,!0),a,d,e)}}),m.each({slideDown:gb("show"),slideUp:gb("hide"),slideToggle:gb("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){m.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),m.timers=[],m.fx.tick=function(){var a,b=m.timers,c=0;for($a=m.now();ca ",d=b.getElementsByTagName("a")[0],c=y.createElement("select"),e=c.appendChild(y.createElement("option")),a=b.getElementsByTagName("input")[0],d.style.cssText="top:1px",k.getSetAttribute="t"!==b.className,k.style=/top/.test(d.getAttribute("style")),k.hrefNormalized="/a"===d.getAttribute("href"),k.checkOn=!!a.value,k.optSelected=e.selected,k.enctype=!!y.createElement("form").enctype,c.disabled=!0,k.optDisabled=!e.disabled,a=y.createElement("input"),a.setAttribute("value",""),k.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),k.radioValue="t"===a.value}();var lb=/\r/g;m.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=m.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,m(this).val()):a,null==e?e="":"number"==typeof e?e+="":m.isArray(e)&&(e=m.map(e,function(a){return null==a?"":a+""})),b=m.valHooks[this.type]||m.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=m.valHooks[e.type]||m.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(lb,""):null==c?"":c)}}}),m.extend({valHooks:{option:{get:function(a){var b=m.find.attr(a,"value");return null!=b?b:m.trim(m.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&m.nodeName(c.parentNode,"optgroup"))){if(b=m(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=m.makeArray(b),g=e.length;while(g--)if(d=e[g],m.inArray(m.valHooks.option.get(d),f)>=0)try{d.selected=c=!0}catch(h){d.scrollHeight}else d.selected=!1;return c||(a.selectedIndex=-1),e}}}}),m.each(["radio","checkbox"],function(){m.valHooks[this]={set:function(a,b){return m.isArray(b)?a.checked=m.inArray(m(a).val(),b)>=0:void 0}},k.checkOn||(m.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var mb,nb,ob=m.expr.attrHandle,pb=/^(?:checked|selected)$/i,qb=k.getSetAttribute,rb=k.input;m.fn.extend({attr:function(a,b){return V(this,m.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){m.removeAttr(this,a)})}}),m.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===K?m.prop(a,b,c):(1===f&&m.isXMLDoc(a)||(b=b.toLowerCase(),d=m.attrHooks[b]||(m.expr.match.bool.test(b)?nb:mb)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=m.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void m.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=m.propFix[c]||c,m.expr.match.bool.test(c)?rb&&qb||!pb.test(c)?a[d]=!1:a[m.camelCase("default-"+c)]=a[d]=!1:m.attr(a,c,""),a.removeAttribute(qb?c:d)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&m.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),nb={set:function(a,b,c){return b===!1?m.removeAttr(a,c):rb&&qb||!pb.test(c)?a.setAttribute(!qb&&m.propFix[c]||c,c):a[m.camelCase("default-"+c)]=a[c]=!0,c}},m.each(m.expr.match.bool.source.match(/\w+/g),function(a,b){var c=ob[b]||m.find.attr;ob[b]=rb&&qb||!pb.test(b)?function(a,b,d){var e,f;return d||(f=ob[b],ob[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,ob[b]=f),e}:function(a,b,c){return c?void 0:a[m.camelCase("default-"+b)]?b.toLowerCase():null}}),rb&&qb||(m.attrHooks.value={set:function(a,b,c){return m.nodeName(a,"input")?void(a.defaultValue=b):mb&&mb.set(a,b,c)}}),qb||(mb={set:function(a,b,c){var d=a.getAttributeNode(c);return d||a.setAttributeNode(d=a.ownerDocument.createAttribute(c)),d.value=b+="","value"===c||b===a.getAttribute(c)?b:void 0}},ob.id=ob.name=ob.coords=function(a,b,c){var d;return c?void 0:(d=a.getAttributeNode(b))&&""!==d.value?d.value:null},m.valHooks.button={get:function(a,b){var c=a.getAttributeNode(b);return c&&c.specified?c.value:void 0},set:mb.set},m.attrHooks.contenteditable={set:function(a,b,c){mb.set(a,""===b?!1:b,c)}},m.each(["width","height"],function(a,b){m.attrHooks[b]={set:function(a,c){return""===c?(a.setAttribute(b,"auto"),c):void 0}}})),k.style||(m.attrHooks.style={get:function(a){return a.style.cssText||void 0},set:function(a,b){return a.style.cssText=b+""}});var sb=/^(?:input|select|textarea|button|object)$/i,tb=/^(?:a|area)$/i;m.fn.extend({prop:function(a,b){return V(this,m.prop,a,b,arguments.length>1)},removeProp:function(a){return a=m.propFix[a]||a,this.each(function(){try{this[a]=void 0,delete this[a]}catch(b){}})}}),m.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!m.isXMLDoc(a),f&&(b=m.propFix[b]||b,e=m.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=m.find.attr(a,"tabindex");return b?parseInt(b,10):sb.test(a.nodeName)||tb.test(a.nodeName)&&a.href?0:-1}}}}),k.hrefNormalized||m.each(["href","src"],function(a,b){m.propHooks[b]={get:function(a){return a.getAttribute(b,4)}}}),k.optSelected||(m.propHooks.selected={get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}}),m.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){m.propFix[this.toLowerCase()]=this}),k.enctype||(m.propFix.enctype="encoding");var ub=/[\t\r\n\f]/g;m.fn.extend({addClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j="string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).addClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ub," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=m.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j=0===arguments.length||"string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).removeClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ub," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?m.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(m.isFunction(a)?function(c){m(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=m(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===K||"boolean"===c)&&(this.className&&m._data(this,"__className__",this.className),this.className=this.className||a===!1?"":m._data(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(ub," ").indexOf(b)>=0)return!0;return!1}}),m.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){m.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),m.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var vb=m.now(),wb=/\?/,xb=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;m.parseJSON=function(b){if(a.JSON&&a.JSON.parse)return a.JSON.parse(b+"");var c,d=null,e=m.trim(b+"");return e&&!m.trim(e.replace(xb,function(a,b,e,f){return c&&b&&(d=0),0===d?a:(c=e||b,d+=!f-!e,"")}))?Function("return "+e)():m.error("Invalid JSON: "+b)},m.parseXML=function(b){var c,d;if(!b||"string"!=typeof b)return null;try{a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b))}catch(e){c=void 0}return c&&c.documentElement&&!c.getElementsByTagName("parsererror").length||m.error("Invalid XML: "+b),c};var yb,zb,Ab=/#.*$/,Bb=/([?&])_=[^&]*/,Cb=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Db=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Eb=/^(?:GET|HEAD)$/,Fb=/^\/\//,Gb=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Hb={},Ib={},Jb="*/".concat("*");try{zb=location.href}catch(Kb){zb=y.createElement("a"),zb.href="",zb=zb.href}yb=Gb.exec(zb.toLowerCase())||[];function Lb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(m.isFunction(c))while(d=f[e++])"+"===d.charAt(0)?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Mb(a,b,c,d){var e={},f=a===Ib;function g(h){var i;return e[h]=!0,m.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Nb(a,b){var c,d,e=m.ajaxSettings.flatOptions||{};for(d in b)void 0!==b[d]&&((e[d]?a:c||(c={}))[d]=b[d]);return c&&m.extend(!0,a,c),a}function Ob(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===e&&(e=a.mimeType||b.getResponseHeader("Content-Type"));if(e)for(g in h)if(h[g]&&h[g].test(e)){i.unshift(g);break}if(i[0]in c)f=i[0];else{for(g in c){if(!i[0]||a.converters[g+" "+i[0]]){f=g;break}d||(d=g)}f=f||d}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function Pb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}m.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:zb,type:"GET",isLocal:Db.test(yb[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Jb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":m.parseJSON,"text xml":m.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Nb(Nb(a,m.ajaxSettings),b):Nb(m.ajaxSettings,a)},ajaxPrefilter:Lb(Hb),ajaxTransport:Lb(Ib),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=m.ajaxSetup({},b),l=k.context||k,n=k.context&&(l.nodeType||l.jquery)?m(l):m.event,o=m.Deferred(),p=m.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!j){j={};while(b=Cb.exec(f))j[b[1].toLowerCase()]=b[2]}b=j[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?f:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return i&&i.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||zb)+"").replace(Ab,"").replace(Fb,yb[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=m.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(c=Gb.exec(k.url.toLowerCase()),k.crossDomain=!(!c||c[1]===yb[1]&&c[2]===yb[2]&&(c[3]||("http:"===c[1]?"80":"443"))===(yb[3]||("http:"===yb[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=m.param(k.data,k.traditional)),Mb(Hb,k,b,v),2===t)return v;h=m.event&&k.global,h&&0===m.active++&&m.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!Eb.test(k.type),e=k.url,k.hasContent||(k.data&&(e=k.url+=(wb.test(e)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=Bb.test(e)?e.replace(Bb,"$1_="+vb++):e+(wb.test(e)?"&":"?")+"_="+vb++)),k.ifModified&&(m.lastModified[e]&&v.setRequestHeader("If-Modified-Since",m.lastModified[e]),m.etag[e]&&v.setRequestHeader("If-None-Match",m.etag[e])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+Jb+"; q=0.01":""):k.accepts["*"]);for(d in k.headers)v.setRequestHeader(d,k.headers[d]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(d in{success:1,error:1,complete:1})v[d](k[d]);if(i=Mb(Ib,k,b,v)){v.readyState=1,h&&n.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,i.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,c,d){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),i=void 0,f=d||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,c&&(u=Ob(k,v,c)),u=Pb(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(m.lastModified[e]=w),w=v.getResponseHeader("etag"),w&&(m.etag[e]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,h&&n.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),h&&(n.trigger("ajaxComplete",[v,k]),--m.active||m.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return m.get(a,b,c,"json")},getScript:function(a,b){return m.get(a,void 0,b,"script")}}),m.each(["get","post"],function(a,b){m[b]=function(a,c,d,e){return m.isFunction(c)&&(e=e||d,d=c,c=void 0),m.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),m._evalUrl=function(a){return m.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},m.fn.extend({wrapAll:function(a){if(m.isFunction(a))return this.each(function(b){m(this).wrapAll(a.call(this,b))});if(this[0]){var b=m(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&1===a.firstChild.nodeType)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return this.each(m.isFunction(a)?function(b){m(this).wrapInner(a.call(this,b))}:function(){var b=m(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=m.isFunction(a);return this.each(function(c){m(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){m.nodeName(this,"body")||m(this).replaceWith(this.childNodes)}).end()}}),m.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0||!k.reliableHiddenOffsets()&&"none"===(a.style&&a.style.display||m.css(a,"display"))},m.expr.filters.visible=function(a){return!m.expr.filters.hidden(a)};var Qb=/%20/g,Rb=/\[\]$/,Sb=/\r?\n/g,Tb=/^(?:submit|button|image|reset|file)$/i,Ub=/^(?:input|select|textarea|keygen)/i;function Vb(a,b,c,d){var e;if(m.isArray(b))m.each(b,function(b,e){c||Rb.test(a)?d(a,e):Vb(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==m.type(b))d(a,b);else for(e in b)Vb(a+"["+e+"]",b[e],c,d)}m.param=function(a,b){var c,d=[],e=function(a,b){b=m.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=m.ajaxSettings&&m.ajaxSettings.traditional),m.isArray(a)||a.jquery&&!m.isPlainObject(a))m.each(a,function(){e(this.name,this.value)});else for(c in a)Vb(c,a[c],b,e);return d.join("&").replace(Qb,"+")},m.fn.extend({serialize:function(){return m.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=m.prop(this,"elements");return a?m.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!m(this).is(":disabled")&&Ub.test(this.nodeName)&&!Tb.test(a)&&(this.checked||!W.test(a))}).map(function(a,b){var c=m(this).val();return null==c?null:m.isArray(c)?m.map(c,function(a){return{name:b.name,value:a.replace(Sb,"\r\n")}}):{name:b.name,value:c.replace(Sb,"\r\n")}}).get()}}),m.ajaxSettings.xhr=void 0!==a.ActiveXObject?function(){return!this.isLocal&&/^(get|post|head|put|delete|options)$/i.test(this.type)&&Zb()||$b()}:Zb;var Wb=0,Xb={},Yb=m.ajaxSettings.xhr();a.attachEvent&&a.attachEvent("onunload",function(){for(var a in Xb)Xb[a](void 0,!0)}),k.cors=!!Yb&&"withCredentials"in Yb,Yb=k.ajax=!!Yb,Yb&&m.ajaxTransport(function(a){if(!a.crossDomain||k.cors){var b;return{send:function(c,d){var e,f=a.xhr(),g=++Wb;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)void 0!==c[e]&&f.setRequestHeader(e,c[e]+"");f.send(a.hasContent&&a.data||null),b=function(c,e){var h,i,j;if(b&&(e||4===f.readyState))if(delete Xb[g],b=void 0,f.onreadystatechange=m.noop,e)4!==f.readyState&&f.abort();else{j={},h=f.status,"string"==typeof f.responseText&&(j.text=f.responseText);try{i=f.statusText}catch(k){i=""}h||!a.isLocal||a.crossDomain?1223===h&&(h=204):h=j.text?200:404}j&&d(h,i,j,f.getAllResponseHeaders())},a.async?4===f.readyState?setTimeout(b):f.onreadystatechange=Xb[g]=b:b()},abort:function(){b&&b(void 0,!0)}}}});function Zb(){try{return new a.XMLHttpRequest}catch(b){}}function $b(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}m.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return m.globalEval(a),a}}}),m.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),m.ajaxTransport("script",function(a){if(a.crossDomain){var b,c=y.head||m("head")[0]||y.documentElement;return{send:function(d,e){b=y.createElement("script"),b.async=!0,a.scriptCharset&&(b.charset=a.scriptCharset),b.src=a.url,b.onload=b.onreadystatechange=function(a,c){(c||!b.readyState||/loaded|complete/.test(b.readyState))&&(b.onload=b.onreadystatechange=null,b.parentNode&&b.parentNode.removeChild(b),b=null,c||e(200,"success"))},c.insertBefore(b,c.firstChild)},abort:function(){b&&b.onload(void 0,!0)}}}});var _b=[],ac=/(=)\?(?=&|$)|\?\?/;m.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=_b.pop()||m.expando+"_"+vb++;return this[a]=!0,a}}),m.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(ac.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&ac.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=m.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(ac,"$1"+e):b.jsonp!==!1&&(b.url+=(wb.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||m.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,_b.push(e)),g&&m.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),m.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||y;var d=u.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=m.buildFragment([a],b,e),e&&e.length&&m(e).remove(),m.merge([],d.childNodes))};var bc=m.fn.load;m.fn.load=function(a,b,c){if("string"!=typeof a&&bc)return bc.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=m.trim(a.slice(h,a.length)),a=a.slice(0,h)),m.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(f="POST"),g.length>0&&m.ajax({url:a,type:f,dataType:"html",data:b}).done(function(a){e=arguments,g.html(d?m("").append(m.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},m.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){m.fn[b]=function(a){return this.on(b,a)}}),m.expr.filters.animated=function(a){return m.grep(m.timers,function(b){return a===b.elem}).length};var cc=a.document.documentElement;function dc(a){return m.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}m.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=m.css(a,"position"),l=m(a),n={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=m.css(a,"top"),i=m.css(a,"left"),j=("absolute"===k||"fixed"===k)&&m.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),m.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(n.top=b.top-h.top+g),null!=b.left&&(n.left=b.left-h.left+e),"using"in b?b.using.call(a,n):l.css(n)}},m.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){m.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,m.contains(b,e)?(typeof e.getBoundingClientRect!==K&&(d=e.getBoundingClientRect()),c=dc(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===m.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),m.nodeName(a[0],"html")||(c=a.offset()),c.top+=m.css(a[0],"borderTopWidth",!0),c.left+=m.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-m.css(d,"marginTop",!0),left:b.left-c.left-m.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||cc;while(a&&!m.nodeName(a,"html")&&"static"===m.css(a,"position"))a=a.offsetParent;return a||cc})}}),m.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);m.fn[a]=function(d){return V(this,function(a,d,e){var f=dc(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?m(f).scrollLeft():e,c?e:m(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),m.each(["top","left"],function(a,b){m.cssHooks[b]=La(k.pixelPosition,function(a,c){return c?(c=Ja(a,b),Ha.test(c)?m(a).position()[b]+"px":c):void 0})}),m.each({Height:"height",Width:"width"},function(a,b){m.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){m.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return V(this,function(b,c,d){var e;return m.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?m.css(b,c,g):m.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),m.fn.size=function(){return this.length},m.fn.andSelf=m.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return m});var ec=a.jQuery,fc=a.$;return m.noConflict=function(b){return a.$===m&&(a.$=fc),b&&a.jQuery===m&&(a.jQuery=ec),m},typeof b===K&&(a.jQuery=a.$=m),m});
diff --git a/Public/libs/jquery/1.x/jquery.min.map b/Public/libs/jquery/1.x/jquery.min.map
new file mode 100644
index 00000000..01f1f980
--- /dev/null
+++ b/Public/libs/jquery/1.x/jquery.min.map
@@ -0,0 +1 @@
+{"version":3,"file":"jquery-1.11.3.min.js","sources":["jquery-1.11.3.js"],"names":["global","factory","module","exports","document","w","Error","window","this","noGlobal","deletedIds","slice","concat","push","indexOf","class2type","toString","hasOwn","hasOwnProperty","support","version","jQuery","selector","context","fn","init","rtrim","rmsPrefix","rdashAlpha","fcamelCase","all","letter","toUpperCase","prototype","jquery","constructor","length","toArray","call","get","num","pushStack","elems","ret","merge","prevObject","each","callback","args","map","elem","i","apply","arguments","first","eq","last","len","j","end","sort","splice","extend","src","copyIsArray","copy","name","options","clone","target","deep","isFunction","isPlainObject","isArray","undefined","expando","Math","random","replace","isReady","error","msg","noop","obj","type","Array","isWindow","isNumeric","parseFloat","isEmptyObject","key","nodeType","e","ownLast","globalEval","data","trim","execScript","camelCase","string","nodeName","toLowerCase","value","isArraylike","text","makeArray","arr","results","Object","inArray","max","second","grep","invert","callbackInverse","matches","callbackExpect","arg","guid","proxy","tmp","now","Date","split","Sizzle","Expr","getText","isXML","tokenize","compile","select","outermostContext","sortInput","hasDuplicate","setDocument","docElem","documentIsHTML","rbuggyQSA","rbuggyMatches","contains","preferredDoc","dirruns","done","classCache","createCache","tokenCache","compilerCache","sortOrder","a","b","MAX_NEGATIVE","pop","push_native","list","booleans","whitespace","characterEncoding","identifier","attributes","pseudos","rwhitespace","RegExp","rcomma","rcombinators","rattributeQuotes","rpseudo","ridentifier","matchExpr","ID","CLASS","TAG","ATTR","PSEUDO","CHILD","bool","needsContext","rinputs","rheader","rnative","rquickExpr","rsibling","rescape","runescape","funescape","_","escaped","escapedWhitespace","high","String","fromCharCode","unloadHandler","childNodes","els","seed","match","m","groups","old","nid","newContext","newSelector","ownerDocument","exec","getElementById","parentNode","id","getElementsByTagName","getElementsByClassName","qsa","test","getAttribute","setAttribute","toSelector","testContext","join","querySelectorAll","qsaError","removeAttribute","keys","cache","cacheLength","shift","markFunction","assert","div","createElement","removeChild","addHandle","attrs","handler","attrHandle","siblingCheck","cur","diff","sourceIndex","nextSibling","createInputPseudo","createButtonPseudo","createPositionalPseudo","argument","matchIndexes","documentElement","node","hasCompare","parent","doc","defaultView","top","addEventListener","attachEvent","className","appendChild","createComment","getById","getElementsByName","find","filter","attrId","getAttributeNode","tag","innerHTML","input","matchesSelector","webkitMatchesSelector","mozMatchesSelector","oMatchesSelector","msMatchesSelector","disconnectedMatch","compareDocumentPosition","adown","bup","compare","sortDetached","aup","ap","bp","unshift","expr","elements","attr","val","specified","uniqueSort","duplicates","detectDuplicates","sortStable","textContent","firstChild","nodeValue","selectors","createPseudo","relative",">","dir"," ","+","~","preFilter","excess","unquoted","nodeNameSelector","pattern","operator","check","result","what","simple","forward","ofType","xml","outerCache","nodeIndex","start","useCache","lastChild","pseudo","setFilters","idx","matched","not","matcher","unmatched","has","innerText","lang","elemLang","hash","location","root","focus","activeElement","hasFocus","href","tabIndex","enabled","disabled","checked","selected","selectedIndex","empty","header","button","even","odd","lt","gt","radio","checkbox","file","password","image","submit","reset","filters","parseOnly","tokens","soFar","preFilters","cached","addCombinator","combinator","base","checkNonElements","doneName","oldCache","newCache","elementMatcher","matchers","multipleContexts","contexts","condense","newUnmatched","mapped","setMatcher","postFilter","postFinder","postSelector","temp","preMap","postMap","preexisting","matcherIn","matcherOut","matcherFromTokens","checkContext","leadingRelative","implicitRelative","matchContext","matchAnyContext","matcherFromGroupMatchers","elementMatchers","setMatchers","bySet","byElement","superMatcher","outermost","matchedCount","setMatched","contextBackup","dirrunsUnique","token","compiled","div1","defaultValue","unique","isXMLDoc","rneedsContext","rsingleTag","risSimple","winnow","qualifier","self","is","rootjQuery","charAt","parseHTML","ready","rparentsprev","guaranteedUnique","children","contents","next","prev","until","sibling","n","r","targets","closest","l","pos","index","prevAll","add","addBack","parents","parentsUntil","nextAll","nextUntil","prevUntil","siblings","contentDocument","contentWindow","reverse","rnotwhite","optionsCache","createOptions","object","flag","Callbacks","firing","memory","fired","firingLength","firingIndex","firingStart","stack","once","fire","stopOnFalse","disable","remove","lock","locked","fireWith","Deferred","func","tuples","state","promise","always","deferred","fail","then","fns","newDefer","tuple","returned","resolve","reject","progress","notify","pipe","stateString","when","subordinate","resolveValues","remaining","updateFunc","values","progressValues","notifyWith","resolveWith","progressContexts","resolveContexts","readyList","readyWait","holdReady","hold","wait","body","setTimeout","triggerHandler","off","detach","removeEventListener","completed","detachEvent","event","readyState","frameElement","doScroll","doScrollCheck","strundefined","inlineBlockNeedsLayout","container","style","cssText","zoom","offsetWidth","deleteExpando","acceptData","noData","rbrace","rmultiDash","dataAttr","parseJSON","isEmptyDataObject","internalData","pvt","thisCache","internalKey","isNode","toJSON","internalRemoveData","cleanData","applet ","embed ","object ","hasData","removeData","_data","_removeData","queue","dequeue","startLength","hooks","_queueHooks","stop","setter","clearQueue","count","defer","pnum","source","cssExpand","isHidden","el","css","access","chainable","emptyGet","raw","bulk","rcheckableType","fragment","createDocumentFragment","leadingWhitespace","tbody","htmlSerialize","html5Clone","cloneNode","outerHTML","appendChecked","noCloneChecked","checkClone","noCloneEvent","click","eventName","change","focusin","rformElems","rkeyEvent","rmouseEvent","rfocusMorph","rtypenamespace","returnTrue","returnFalse","safeActiveElement","err","types","events","t","handleObjIn","special","eventHandle","handleObj","handlers","namespaces","origType","elemData","handle","triggered","dispatch","delegateType","bindType","namespace","delegateCount","setup","mappedTypes","origCount","teardown","removeEvent","trigger","onlyHandlers","ontype","bubbleType","eventPath","Event","isTrigger","namespace_re","noBubble","parentWindow","isPropagationStopped","preventDefault","isDefaultPrevented","_default","fix","handlerQueue","delegateTarget","preDispatch","currentTarget","isImmediatePropagationStopped","stopPropagation","postDispatch","sel","prop","originalEvent","fixHook","fixHooks","mouseHooks","keyHooks","props","srcElement","metaKey","original","which","charCode","keyCode","eventDoc","fromElement","pageX","clientX","scrollLeft","clientLeft","pageY","clientY","scrollTop","clientTop","relatedTarget","toElement","load","blur","beforeunload","returnValue","simulate","bubble","isSimulated","defaultPrevented","timeStamp","cancelBubble","stopImmediatePropagation","mouseenter","mouseleave","pointerenter","pointerleave","orig","related","submitBubbles","form","_submit_bubble","changeBubbles","propertyName","_just_changed","focusinBubbles","attaches","on","one","origFn","createSafeFragment","nodeNames","safeFrag","rinlinejQuery","rnoshimcache","rleadingWhitespace","rxhtmlTag","rtagName","rtbody","rhtml","rnoInnerhtml","rchecked","rscriptType","rscriptTypeMasked","rcleanScript","wrapMap","option","legend","area","param","thead","tr","col","td","safeFragment","fragmentDiv","optgroup","tfoot","colgroup","caption","th","getAll","found","fixDefaultChecked","defaultChecked","manipulationTarget","content","disableScript","restoreScript","setGlobalEval","refElements","cloneCopyEvent","dest","oldData","curData","fixCloneNodeIssues","defaultSelected","dataAndEvents","deepDataAndEvents","destElements","srcElements","inPage","buildFragment","scripts","selection","wrap","safe","nodes","createTextNode","append","domManip","prepend","insertBefore","before","after","keepData","html","replaceWith","replaceChild","hasScripts","set","iNoClone","_evalUrl","appendTo","prependTo","insertAfter","replaceAll","insert","iframe","elemdisplay","actualDisplay","display","getDefaultComputedStyle","defaultDisplay","write","close","shrinkWrapBlocksVal","shrinkWrapBlocks","width","rmargin","rnumnonpx","getStyles","curCSS","rposition","getComputedStyle","opener","computed","minWidth","maxWidth","getPropertyValue","currentStyle","left","rs","rsLeft","runtimeStyle","pixelLeft","addGetHookIf","conditionFn","hookFn","condition","pixelPositionVal","boxSizingReliableVal","reliableHiddenOffsetsVal","reliableMarginRightVal","opacity","cssFloat","backgroundClip","clearCloneStyle","boxSizing","MozBoxSizing","WebkitBoxSizing","reliableHiddenOffsets","computeStyleTests","boxSizingReliable","pixelPosition","reliableMarginRight","marginRight","offsetHeight","swap","ralpha","ropacity","rdisplayswap","rnumsplit","rrelNum","cssShow","position","visibility","cssNormalTransform","letterSpacing","fontWeight","cssPrefixes","vendorPropName","capName","origName","showHide","show","hidden","setPositiveNumber","subtract","augmentWidthOrHeight","extra","isBorderBox","styles","getWidthOrHeight","valueIsBorderBox","cssHooks","cssNumber","columnCount","fillOpacity","flexGrow","flexShrink","lineHeight","order","orphans","widows","zIndex","cssProps","float","$1","margin","padding","border","prefix","suffix","expand","expanded","parts","hide","toggle","Tween","easing","unit","propHooks","run","percent","eased","duration","step","tween","fx","linear","p","swing","cos","PI","fxNow","timerId","rfxtypes","rfxnum","rrun","animationPrefilters","defaultPrefilter","tweeners","*","createTween","scale","maxIterations","createFxNow","genFx","includeWidth","height","animation","collection","opts","oldfire","checkDisplay","anim","dataShow","unqueued","overflow","overflowX","overflowY","propFilter","specialEasing","Animation","properties","stopped","tick","currentTime","startTime","tweens","originalProperties","originalOptions","gotoEnd","rejectWith","timer","complete","tweener","prefilter","speed","opt","speeds","fadeTo","to","animate","optall","doAnimation","finish","stopQueue","timers","cssFn","slideDown","slideUp","slideToggle","fadeIn","fadeOut","fadeToggle","interval","setInterval","clearInterval","slow","fast","delay","time","timeout","clearTimeout","getSetAttribute","hrefNormalized","checkOn","optSelected","enctype","optDisabled","radioValue","rreturn","valHooks","optionSet","scrollHeight","nodeHook","boolHook","ruseDefault","getSetInput","removeAttr","nType","attrHooks","propName","attrNames","propFix","getter","setAttributeNode","createAttribute","coords","contenteditable","rfocusable","rclickable","removeProp","for","class","notxml","tabindex","parseInt","rclass","addClass","classes","clazz","finalValue","proceed","removeClass","toggleClass","stateVal","classNames","hasClass","hover","fnOver","fnOut","bind","unbind","delegate","undelegate","nonce","rquery","rvalidtokens","JSON","parse","requireNonComma","depth","str","comma","open","Function","parseXML","DOMParser","parseFromString","ActiveXObject","async","loadXML","ajaxLocParts","ajaxLocation","rhash","rts","rheaders","rlocalProtocol","rnoContent","rprotocol","rurl","prefilters","transports","allTypes","addToPrefiltersOrTransports","structure","dataTypeExpression","dataType","dataTypes","inspectPrefiltersOrTransports","jqXHR","inspected","seekingTransport","inspect","prefilterOrFactory","dataTypeOrTransport","ajaxExtend","flatOptions","ajaxSettings","ajaxHandleResponses","s","responses","firstDataType","ct","finalDataType","mimeType","getResponseHeader","converters","ajaxConvert","response","isSuccess","conv2","current","conv","responseFields","dataFilter","active","lastModified","etag","url","isLocal","processData","contentType","accepts","json","* text","text html","text json","text xml","ajaxSetup","settings","ajaxPrefilter","ajaxTransport","ajax","cacheURL","responseHeadersString","timeoutTimer","fireGlobals","transport","responseHeaders","callbackContext","globalEventContext","completeDeferred","statusCode","requestHeaders","requestHeadersNames","strAbort","getAllResponseHeaders","setRequestHeader","lname","overrideMimeType","code","status","abort","statusText","finalText","success","method","crossDomain","traditional","hasContent","ifModified","headers","beforeSend","send","nativeStatusText","modified","getJSON","getScript","throws","wrapAll","wrapInner","unwrap","visible","r20","rbracket","rCRLF","rsubmitterTypes","rsubmittable","buildParams","v","encodeURIComponent","serialize","serializeArray","xhr","createStandardXHR","createActiveXHR","xhrId","xhrCallbacks","xhrSupported","cors","username","xhrFields","isAbort","onreadystatechange","responseText","XMLHttpRequest","script","text script","head","scriptCharset","charset","onload","oldCallbacks","rjsonp","jsonp","jsonpCallback","originalSettings","callbackName","overwritten","responseContainer","jsonProp","keepScripts","parsed","_load","params","animated","getWindow","offset","setOffset","curPosition","curLeft","curCSSTop","curTop","curOffset","curCSSLeft","calculatePosition","curElem","using","win","box","getBoundingClientRect","pageYOffset","pageXOffset","offsetParent","parentOffset","scrollTo","Height","Width","defaultExtra","funcName","size","andSelf","define","amd","_jQuery","_$","$","noConflict"],"mappings":"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"}
\ No newline at end of file
diff --git a/Public/libs/jquery/2.x/jquery.js b/Public/libs/jquery/2.x/jquery.js
new file mode 100644
index 00000000..eed17778
--- /dev/null
+++ b/Public/libs/jquery/2.x/jquery.js
@@ -0,0 +1,9210 @@
+/*!
+ * jQuery JavaScript Library v2.1.4
+ * http://jquery.com/
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ *
+ * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2015-04-28T16:01Z
+ */
+
+(function( global, factory ) {
+
+ if ( typeof module === "object" && typeof module.exports === "object" ) {
+ // For CommonJS and CommonJS-like environments where a proper `window`
+ // is present, execute the factory and get jQuery.
+ // For environments that do not have a `window` with a `document`
+ // (such as Node.js), expose a factory as module.exports.
+ // This accentuates the need for the creation of a real `window`.
+ // e.g. var jQuery = require("jquery")(window);
+ // See ticket #14549 for more info.
+ module.exports = global.document ?
+ factory( global, true ) :
+ function( w ) {
+ if ( !w.document ) {
+ throw new Error( "jQuery requires a window with a document" );
+ }
+ return factory( w );
+ };
+ } else {
+ factory( global );
+ }
+
+// Pass this if window is not defined yet
+}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+
+// Support: Firefox 18+
+// Can't be in strict mode, several libs including ASP.NET trace
+// the stack via arguments.caller.callee and Firefox dies if
+// you try to trace through "use strict" call chains. (#13335)
+//
+
+var arr = [];
+
+var slice = arr.slice;
+
+var concat = arr.concat;
+
+var push = arr.push;
+
+var indexOf = arr.indexOf;
+
+var class2type = {};
+
+var toString = class2type.toString;
+
+var hasOwn = class2type.hasOwnProperty;
+
+var support = {};
+
+
+
+var
+ // Use the correct document accordingly with window argument (sandbox)
+ document = window.document,
+
+ version = "2.1.4",
+
+ // Define a local copy of jQuery
+ jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ // Need init if jQuery is called (just allow error to be thrown if not included)
+ return new jQuery.fn.init( selector, context );
+ },
+
+ // Support: Android<4.1
+ // Make sure we trim BOM and NBSP
+ rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+
+ // Matches dashed string for camelizing
+ rmsPrefix = /^-ms-/,
+ rdashAlpha = /-([\da-z])/gi,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return letter.toUpperCase();
+ };
+
+jQuery.fn = jQuery.prototype = {
+ // The current version of jQuery being used
+ jquery: version,
+
+ constructor: jQuery,
+
+ // Start with an empty selector
+ selector: "",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ toArray: function() {
+ return slice.call( this );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num != null ?
+
+ // Return just the one element from the set
+ ( num < 0 ? this[ num + this.length ] : this[ num ] ) :
+
+ // Return all the elements in a clean array
+ slice.call( this );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems ) {
+
+ // Build a new jQuery matched element set
+ var ret = jQuery.merge( this.constructor(), elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+ ret.context = this.context;
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ slice: function() {
+ return this.pushStack( slice.apply( this, arguments ) );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ eq: function( i ) {
+ var len = this.length,
+ j = +i + ( i < 0 ? len : 0 );
+ return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: push,
+ sort: arr.sort,
+ splice: arr.splice
+};
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+
+ // Skip the boolean and the target
+ target = arguments[ i ] || {};
+ i++;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // Extend jQuery itself if only one argument is passed
+ if ( i === length ) {
+ target = this;
+ i--;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ // Unique for each copy of jQuery on the page
+ expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
+
+ // Assume jQuery is ready without the ready module
+ isReady: true,
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ noop: function() {},
+
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray,
+
+ isWindow: function( obj ) {
+ return obj != null && obj === obj.window;
+ },
+
+ isNumeric: function( obj ) {
+ // parseFloat NaNs numeric-cast false positives (null|true|false|"")
+ // ...but misinterprets leading-number strings, particularly hex literals ("0x...")
+ // subtraction forces infinities to NaN
+ // adding 1 corrects loss of precision from parseFloat (#15100)
+ return !jQuery.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0;
+ },
+
+ isPlainObject: function( obj ) {
+ // Not plain objects:
+ // - Any object or value whose internal [[Class]] property is not "[object Object]"
+ // - DOM nodes
+ // - window
+ if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ if ( obj.constructor &&
+ !hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) {
+ return false;
+ }
+
+ // If the function hasn't returned already, we're confident that
+ // |obj| is a plain object, created by {} or constructed with new Object
+ return true;
+ },
+
+ isEmptyObject: function( obj ) {
+ var name;
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ type: function( obj ) {
+ if ( obj == null ) {
+ return obj + "";
+ }
+ // Support: Android<4.0, iOS<6 (functionish RegExp)
+ return typeof obj === "object" || typeof obj === "function" ?
+ class2type[ toString.call(obj) ] || "object" :
+ typeof obj;
+ },
+
+ // Evaluates a script in a global context
+ globalEval: function( code ) {
+ var script,
+ indirect = eval;
+
+ code = jQuery.trim( code );
+
+ if ( code ) {
+ // If the code includes a valid, prologue position
+ // strict mode pragma, execute code by injecting a
+ // script tag into the document.
+ if ( code.indexOf("use strict") === 1 ) {
+ script = document.createElement("script");
+ script.text = code;
+ document.head.appendChild( script ).parentNode.removeChild( script );
+ } else {
+ // Otherwise, avoid the DOM node creation, insertion
+ // and removal by using an indirect global eval
+ indirect( code );
+ }
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Support: IE9-11+
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+ },
+
+ // args is for internal usage only
+ each: function( obj, callback, args ) {
+ var value,
+ i = 0,
+ length = obj.length,
+ isArray = isArraylike( obj );
+
+ if ( args ) {
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback.apply( obj[ i ], args );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ value = callback.apply( obj[ i ], args );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback.call( obj[ i ], i, obj[ i ] );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ value = callback.call( obj[ i ], i, obj[ i ] );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return obj;
+ },
+
+ // Support: Android<4.1
+ trim: function( text ) {
+ return text == null ?
+ "" :
+ ( text + "" ).replace( rtrim, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( arr, results ) {
+ var ret = results || [];
+
+ if ( arr != null ) {
+ if ( isArraylike( Object(arr) ) ) {
+ jQuery.merge( ret,
+ typeof arr === "string" ?
+ [ arr ] : arr
+ );
+ } else {
+ push.call( ret, arr );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, arr, i ) {
+ return arr == null ? -1 : indexOf.call( arr, elem, i );
+ },
+
+ merge: function( first, second ) {
+ var len = +second.length,
+ j = 0,
+ i = first.length;
+
+ for ( ; j < len; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, invert ) {
+ var callbackInverse,
+ matches = [],
+ i = 0,
+ length = elems.length,
+ callbackExpect = !invert;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( ; i < length; i++ ) {
+ callbackInverse = !callback( elems[ i ], i );
+ if ( callbackInverse !== callbackExpect ) {
+ matches.push( elems[ i ] );
+ }
+ }
+
+ return matches;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value,
+ i = 0,
+ length = elems.length,
+ isArray = isArraylike( elems ),
+ ret = [];
+
+ // Go through the array, translating each of the items to their new values
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret.push( value );
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( i in elems ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret.push( value );
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ var tmp, args, proxy;
+
+ if ( typeof context === "string" ) {
+ tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ args = slice.call( arguments, 2 );
+ proxy = function() {
+ return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ now: Date.now,
+
+ // jQuery.support is not used in Core but other projects attach their
+ // properties to it so it needs to exist.
+ support: support
+});
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+function isArraylike( obj ) {
+
+ // Support: iOS 8.2 (not reproducible in simulator)
+ // `in` check used to prevent JIT error (gh-2145)
+ // hasOwn isn't used here due to false negatives
+ // regarding Nodelist length in IE
+ var length = "length" in obj && obj.length,
+ type = jQuery.type( obj );
+
+ if ( type === "function" || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ if ( obj.nodeType === 1 && length ) {
+ return true;
+ }
+
+ return type === "array" || length === 0 ||
+ typeof length === "number" && length > 0 && ( length - 1 ) in obj;
+}
+var Sizzle =
+/*!
+ * Sizzle CSS Selector Engine v2.2.0-pre
+ * http://sizzlejs.com/
+ *
+ * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2014-12-16
+ */
+(function( window ) {
+
+var i,
+ support,
+ Expr,
+ getText,
+ isXML,
+ tokenize,
+ compile,
+ select,
+ outermostContext,
+ sortInput,
+ hasDuplicate,
+
+ // Local document vars
+ setDocument,
+ document,
+ docElem,
+ documentIsHTML,
+ rbuggyQSA,
+ rbuggyMatches,
+ matches,
+ contains,
+
+ // Instance-specific data
+ expando = "sizzle" + 1 * new Date(),
+ preferredDoc = window.document,
+ dirruns = 0,
+ done = 0,
+ classCache = createCache(),
+ tokenCache = createCache(),
+ compilerCache = createCache(),
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ },
+
+ // General-purpose constants
+ MAX_NEGATIVE = 1 << 31,
+
+ // Instance methods
+ hasOwn = ({}).hasOwnProperty,
+ arr = [],
+ pop = arr.pop,
+ push_native = arr.push,
+ push = arr.push,
+ slice = arr.slice,
+ // Use a stripped-down indexOf as it's faster than native
+ // http://jsperf.com/thor-indexof-vs-for/5
+ indexOf = function( list, elem ) {
+ var i = 0,
+ len = list.length;
+ for ( ; i < len; i++ ) {
+ if ( list[i] === elem ) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
+
+ // Regular expressions
+
+ // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
+ whitespace = "[\\x20\\t\\r\\n\\f]",
+ // http://www.w3.org/TR/css3-syntax/#characters
+ characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
+
+ // Loosely modeled on CSS identifier characters
+ // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
+ // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+ identifier = characterEncoding.replace( "w", "w#" ),
+
+ // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
+ attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace +
+ // Operator (capture 2)
+ "*([*^$|!~]?=)" + whitespace +
+ // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
+ "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
+ "*\\]",
+
+ pseudos = ":(" + characterEncoding + ")(?:\\((" +
+ // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
+ // 1. quoted (capture 3; capture 4 or capture 5)
+ "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
+ // 2. simple (capture 6)
+ "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
+ // 3. anything else (capture 2)
+ ".*" +
+ ")\\)|)",
+
+ // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+ rwhitespace = new RegExp( whitespace + "+", "g" ),
+ rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
+
+ rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+ rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
+
+ rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
+
+ rpseudo = new RegExp( pseudos ),
+ ridentifier = new RegExp( "^" + identifier + "$" ),
+
+ matchExpr = {
+ "ID": new RegExp( "^#(" + characterEncoding + ")" ),
+ "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
+ "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
+ "ATTR": new RegExp( "^" + attributes ),
+ "PSEUDO": new RegExp( "^" + pseudos ),
+ "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
+ "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
+ "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+ "bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
+ // For use in libraries implementing .is()
+ // We use this for POS matching in `select`
+ "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
+ whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
+ },
+
+ rinputs = /^(?:input|select|textarea|button)$/i,
+ rheader = /^h\d$/i,
+
+ rnative = /^[^{]+\{\s*\[native \w/,
+
+ // Easily-parseable/retrievable ID or TAG or CLASS selectors
+ rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
+
+ rsibling = /[+~]/,
+ rescape = /'|\\/g,
+
+ // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
+ runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
+ funescape = function( _, escaped, escapedWhitespace ) {
+ var high = "0x" + escaped - 0x10000;
+ // NaN means non-codepoint
+ // Support: Firefox<24
+ // Workaround erroneous numeric interpretation of +"0x"
+ return high !== high || escapedWhitespace ?
+ escaped :
+ high < 0 ?
+ // BMP codepoint
+ String.fromCharCode( high + 0x10000 ) :
+ // Supplemental Plane codepoint (surrogate pair)
+ String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
+ },
+
+ // Used for iframes
+ // See setDocument()
+ // Removing the function wrapper causes a "Permission Denied"
+ // error in IE
+ unloadHandler = function() {
+ setDocument();
+ };
+
+// Optimize for push.apply( _, NodeList )
+try {
+ push.apply(
+ (arr = slice.call( preferredDoc.childNodes )),
+ preferredDoc.childNodes
+ );
+ // Support: Android<4.0
+ // Detect silently failing push.apply
+ arr[ preferredDoc.childNodes.length ].nodeType;
+} catch ( e ) {
+ push = { apply: arr.length ?
+
+ // Leverage slice if possible
+ function( target, els ) {
+ push_native.apply( target, slice.call(els) );
+ } :
+
+ // Support: IE<9
+ // Otherwise append directly
+ function( target, els ) {
+ var j = target.length,
+ i = 0;
+ // Can't trust NodeList.length
+ while ( (target[j++] = els[i++]) ) {}
+ target.length = j - 1;
+ }
+ };
+}
+
+function Sizzle( selector, context, results, seed ) {
+ var match, elem, m, nodeType,
+ // QSA vars
+ i, groups, old, nid, newContext, newSelector;
+
+ if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
+ setDocument( context );
+ }
+
+ context = context || document;
+ results = results || [];
+ nodeType = context.nodeType;
+
+ if ( typeof selector !== "string" || !selector ||
+ nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
+
+ return results;
+ }
+
+ if ( !seed && documentIsHTML ) {
+
+ // Try to shortcut find operations when possible (e.g., not under DocumentFragment)
+ if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
+ // Speed-up: Sizzle("#ID")
+ if ( (m = match[1]) ) {
+ if ( nodeType === 9 ) {
+ elem = context.getElementById( m );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document (jQuery #6963)
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE, Opera, and Webkit return items
+ // by name instead of ID
+ if ( elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ } else {
+ return results;
+ }
+ } else {
+ // Context is not a document
+ if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
+ contains( context, elem ) && elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ }
+
+ // Speed-up: Sizzle("TAG")
+ } else if ( match[2] ) {
+ push.apply( results, context.getElementsByTagName( selector ) );
+ return results;
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( (m = match[3]) && support.getElementsByClassName ) {
+ push.apply( results, context.getElementsByClassName( m ) );
+ return results;
+ }
+ }
+
+ // QSA path
+ if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+ nid = old = expando;
+ newContext = context;
+ newSelector = nodeType !== 1 && selector;
+
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ groups = tokenize( selector );
+
+ if ( (old = context.getAttribute("id")) ) {
+ nid = old.replace( rescape, "\\$&" );
+ } else {
+ context.setAttribute( "id", nid );
+ }
+ nid = "[id='" + nid + "'] ";
+
+ i = groups.length;
+ while ( i-- ) {
+ groups[i] = nid + toSelector( groups[i] );
+ }
+ newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
+ newSelector = groups.join(",");
+ }
+
+ if ( newSelector ) {
+ try {
+ push.apply( results,
+ newContext.querySelectorAll( newSelector )
+ );
+ return results;
+ } catch(qsaError) {
+ } finally {
+ if ( !old ) {
+ context.removeAttribute("id");
+ }
+ }
+ }
+ }
+ }
+
+ // All others
+ return select( selector.replace( rtrim, "$1" ), context, results, seed );
+}
+
+/**
+ * Create key-value caches of limited size
+ * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
+ * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
+ * deleting the oldest entry
+ */
+function createCache() {
+ var keys = [];
+
+ function cache( key, value ) {
+ // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
+ if ( keys.push( key + " " ) > Expr.cacheLength ) {
+ // Only keep the most recent entries
+ delete cache[ keys.shift() ];
+ }
+ return (cache[ key + " " ] = value);
+ }
+ return cache;
+}
+
+/**
+ * Mark a function for special use by Sizzle
+ * @param {Function} fn The function to mark
+ */
+function markFunction( fn ) {
+ fn[ expando ] = true;
+ return fn;
+}
+
+/**
+ * Support testing using an element
+ * @param {Function} fn Passed the created div and expects a boolean result
+ */
+function assert( fn ) {
+ var div = document.createElement("div");
+
+ try {
+ return !!fn( div );
+ } catch (e) {
+ return false;
+ } finally {
+ // Remove from its parent by default
+ if ( div.parentNode ) {
+ div.parentNode.removeChild( div );
+ }
+ // release memory in IE
+ div = null;
+ }
+}
+
+/**
+ * Adds the same handler for all of the specified attrs
+ * @param {String} attrs Pipe-separated list of attributes
+ * @param {Function} handler The method that will be applied
+ */
+function addHandle( attrs, handler ) {
+ var arr = attrs.split("|"),
+ i = attrs.length;
+
+ while ( i-- ) {
+ Expr.attrHandle[ arr[i] ] = handler;
+ }
+}
+
+/**
+ * Checks document order of two siblings
+ * @param {Element} a
+ * @param {Element} b
+ * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
+ */
+function siblingCheck( a, b ) {
+ var cur = b && a,
+ diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
+ ( ~b.sourceIndex || MAX_NEGATIVE ) -
+ ( ~a.sourceIndex || MAX_NEGATIVE );
+
+ // Use IE sourceIndex if available on both nodes
+ if ( diff ) {
+ return diff;
+ }
+
+ // Check if b follows a
+ if ( cur ) {
+ while ( (cur = cur.nextSibling) ) {
+ if ( cur === b ) {
+ return -1;
+ }
+ }
+ }
+
+ return a ? 1 : -1;
+}
+
+/**
+ * Returns a function to use in pseudos for input types
+ * @param {String} type
+ */
+function createInputPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === type;
+ };
+}
+
+/**
+ * Returns a function to use in pseudos for buttons
+ * @param {String} type
+ */
+function createButtonPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && elem.type === type;
+ };
+}
+
+/**
+ * Returns a function to use in pseudos for positionals
+ * @param {Function} fn
+ */
+function createPositionalPseudo( fn ) {
+ return markFunction(function( argument ) {
+ argument = +argument;
+ return markFunction(function( seed, matches ) {
+ var j,
+ matchIndexes = fn( [], seed.length, argument ),
+ i = matchIndexes.length;
+
+ // Match elements found at the specified indexes
+ while ( i-- ) {
+ if ( seed[ (j = matchIndexes[i]) ] ) {
+ seed[j] = !(matches[j] = seed[j]);
+ }
+ }
+ });
+ });
+}
+
+/**
+ * Checks a node for validity as a Sizzle context
+ * @param {Element|Object=} context
+ * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
+ */
+function testContext( context ) {
+ return context && typeof context.getElementsByTagName !== "undefined" && context;
+}
+
+// Expose support vars for convenience
+support = Sizzle.support = {};
+
+/**
+ * Detects XML nodes
+ * @param {Element|Object} elem An element or a document
+ * @returns {Boolean} True iff elem is a non-HTML XML node
+ */
+isXML = Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = elem && (elem.ownerDocument || elem).documentElement;
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+/**
+ * Sets document-related variables once based on the current document
+ * @param {Element|Object} [doc] An element or document object to use to set the document
+ * @returns {Object} Returns the current document
+ */
+setDocument = Sizzle.setDocument = function( node ) {
+ var hasCompare, parent,
+ doc = node ? node.ownerDocument || node : preferredDoc;
+
+ // If no document and documentElement is available, return
+ if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
+ return document;
+ }
+
+ // Set our document
+ document = doc;
+ docElem = doc.documentElement;
+ parent = doc.defaultView;
+
+ // Support: IE>8
+ // If iframe document is assigned to "document" variable and if iframe has been reloaded,
+ // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
+ // IE6-8 do not support the defaultView property so parent will be undefined
+ if ( parent && parent !== parent.top ) {
+ // IE11 does not have attachEvent, so all must suffer
+ if ( parent.addEventListener ) {
+ parent.addEventListener( "unload", unloadHandler, false );
+ } else if ( parent.attachEvent ) {
+ parent.attachEvent( "onunload", unloadHandler );
+ }
+ }
+
+ /* Support tests
+ ---------------------------------------------------------------------- */
+ documentIsHTML = !isXML( doc );
+
+ /* Attributes
+ ---------------------------------------------------------------------- */
+
+ // Support: IE<8
+ // Verify that getAttribute really returns attributes and not properties
+ // (excepting IE8 booleans)
+ support.attributes = assert(function( div ) {
+ div.className = "i";
+ return !div.getAttribute("className");
+ });
+
+ /* getElement(s)By*
+ ---------------------------------------------------------------------- */
+
+ // Check if getElementsByTagName("*") returns only elements
+ support.getElementsByTagName = assert(function( div ) {
+ div.appendChild( doc.createComment("") );
+ return !div.getElementsByTagName("*").length;
+ });
+
+ // Support: IE<9
+ support.getElementsByClassName = rnative.test( doc.getElementsByClassName );
+
+ // Support: IE<10
+ // Check if getElementById returns elements by name
+ // The broken getElementById methods don't pick up programatically-set names,
+ // so use a roundabout getElementsByName test
+ support.getById = assert(function( div ) {
+ docElem.appendChild( div ).id = expando;
+ return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
+ });
+
+ // ID find and filter
+ if ( support.getById ) {
+ Expr.find["ID"] = function( id, context ) {
+ if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
+ var m = context.getElementById( id );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [ m ] : [];
+ }
+ };
+ Expr.filter["ID"] = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ return elem.getAttribute("id") === attrId;
+ };
+ };
+ } else {
+ // Support: IE6/7
+ // getElementById is not reliable as a find shortcut
+ delete Expr.find["ID"];
+
+ Expr.filter["ID"] = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+ return node && node.value === attrId;
+ };
+ };
+ }
+
+ // Tag
+ Expr.find["TAG"] = support.getElementsByTagName ?
+ function( tag, context ) {
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ return context.getElementsByTagName( tag );
+
+ // DocumentFragment nodes don't have gEBTN
+ } else if ( support.qsa ) {
+ return context.querySelectorAll( tag );
+ }
+ } :
+
+ function( tag, context ) {
+ var elem,
+ tmp = [],
+ i = 0,
+ // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
+ results = context.getElementsByTagName( tag );
+
+ // Filter out possible comments
+ if ( tag === "*" ) {
+ while ( (elem = results[i++]) ) {
+ if ( elem.nodeType === 1 ) {
+ tmp.push( elem );
+ }
+ }
+
+ return tmp;
+ }
+ return results;
+ };
+
+ // Class
+ Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
+ if ( documentIsHTML ) {
+ return context.getElementsByClassName( className );
+ }
+ };
+
+ /* QSA/matchesSelector
+ ---------------------------------------------------------------------- */
+
+ // QSA and matchesSelector support
+
+ // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+ rbuggyMatches = [];
+
+ // qSa(:focus) reports false when true (Chrome 21)
+ // We allow this because of a bug in IE8/9 that throws an error
+ // whenever `document.activeElement` is accessed on an iframe
+ // So, we allow :focus to pass through QSA all the time to avoid the IE error
+ // See http://bugs.jquery.com/ticket/13378
+ rbuggyQSA = [];
+
+ if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
+ // Build QSA regex
+ // Regex strategy adopted from Diego Perini
+ assert(function( div ) {
+ // Select is set to empty string on purpose
+ // This is to test IE's treatment of not explicitly
+ // setting a boolean content attribute,
+ // since its presence should be enough
+ // http://bugs.jquery.com/ticket/12359
+ docElem.appendChild( div ).innerHTML = "
" +
+ "
" +
+ " ";
+
+ // Support: IE8, Opera 11-12.16
+ // Nothing should be selected when empty strings follow ^= or $= or *=
+ // The test attribute must be unknown in Opera but "safe" for WinRT
+ // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
+ if ( div.querySelectorAll("[msallowcapture^='']").length ) {
+ rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
+ }
+
+ // Support: IE8
+ // Boolean attributes and "value" are not treated correctly
+ if ( !div.querySelectorAll("[selected]").length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
+ }
+
+ // Support: Chrome<29, Android<4.2+, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.7+
+ if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
+ rbuggyQSA.push("~=");
+ }
+
+ // Webkit/Opera - :checked should return selected option elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ // IE8 throws error here and will not see later tests
+ if ( !div.querySelectorAll(":checked").length ) {
+ rbuggyQSA.push(":checked");
+ }
+
+ // Support: Safari 8+, iOS 8+
+ // https://bugs.webkit.org/show_bug.cgi?id=136851
+ // In-page `selector#id sibing-combinator selector` fails
+ if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) {
+ rbuggyQSA.push(".#.+[+~]");
+ }
+ });
+
+ assert(function( div ) {
+ // Support: Windows 8 Native Apps
+ // The type and name attributes are restricted during .innerHTML assignment
+ var input = doc.createElement("input");
+ input.setAttribute( "type", "hidden" );
+ div.appendChild( input ).setAttribute( "name", "D" );
+
+ // Support: IE8
+ // Enforce case-sensitivity of name attribute
+ if ( div.querySelectorAll("[name=d]").length ) {
+ rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
+ }
+
+ // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+ // IE8 throws error here and will not see later tests
+ if ( !div.querySelectorAll(":enabled").length ) {
+ rbuggyQSA.push( ":enabled", ":disabled" );
+ }
+
+ // Opera 10-11 does not throw on post-comma invalid pseudos
+ div.querySelectorAll("*,:x");
+ rbuggyQSA.push(",.*:");
+ });
+ }
+
+ if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
+ docElem.webkitMatchesSelector ||
+ docElem.mozMatchesSelector ||
+ docElem.oMatchesSelector ||
+ docElem.msMatchesSelector) )) ) {
+
+ assert(function( div ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9)
+ support.disconnectedMatch = matches.call( div, "div" );
+
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( div, "[s!='']:x" );
+ rbuggyMatches.push( "!=", pseudos );
+ });
+ }
+
+ rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
+ rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
+
+ /* Contains
+ ---------------------------------------------------------------------- */
+ hasCompare = rnative.test( docElem.compareDocumentPosition );
+
+ // Element contains another
+ // Purposefully does not implement inclusive descendent
+ // As in, an element does not contain itself
+ contains = hasCompare || rnative.test( docElem.contains ) ?
+ function( a, b ) {
+ var adown = a.nodeType === 9 ? a.documentElement : a,
+ bup = b && b.parentNode;
+ return a === bup || !!( bup && bup.nodeType === 1 && (
+ adown.contains ?
+ adown.contains( bup ) :
+ a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
+ ));
+ } :
+ function( a, b ) {
+ if ( b ) {
+ while ( (b = b.parentNode) ) {
+ if ( b === a ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ /* Sorting
+ ---------------------------------------------------------------------- */
+
+ // Document order sorting
+ sortOrder = hasCompare ?
+ function( a, b ) {
+
+ // Flag for duplicate removal
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ // Sort on method existence if only one input has compareDocumentPosition
+ var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
+ if ( compare ) {
+ return compare;
+ }
+
+ // Calculate position if both inputs belong to the same document
+ compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
+ a.compareDocumentPosition( b ) :
+
+ // Otherwise we know they are disconnected
+ 1;
+
+ // Disconnected nodes
+ if ( compare & 1 ||
+ (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
+
+ // Choose the first element that is related to our preferred document
+ if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
+ return -1;
+ }
+ if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
+ return 1;
+ }
+
+ // Maintain original order
+ return sortInput ?
+ ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+ 0;
+ }
+
+ return compare & 4 ? -1 : 1;
+ } :
+ function( a, b ) {
+ // Exit early if the nodes are identical
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ var cur,
+ i = 0,
+ aup = a.parentNode,
+ bup = b.parentNode,
+ ap = [ a ],
+ bp = [ b ];
+
+ // Parentless nodes are either documents or disconnected
+ if ( !aup || !bup ) {
+ return a === doc ? -1 :
+ b === doc ? 1 :
+ aup ? -1 :
+ bup ? 1 :
+ sortInput ?
+ ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+ 0;
+
+ // If the nodes are siblings, we can do a quick check
+ } else if ( aup === bup ) {
+ return siblingCheck( a, b );
+ }
+
+ // Otherwise we need full lists of their ancestors for comparison
+ cur = a;
+ while ( (cur = cur.parentNode) ) {
+ ap.unshift( cur );
+ }
+ cur = b;
+ while ( (cur = cur.parentNode) ) {
+ bp.unshift( cur );
+ }
+
+ // Walk down the tree looking for a discrepancy
+ while ( ap[i] === bp[i] ) {
+ i++;
+ }
+
+ return i ?
+ // Do a sibling check if the nodes have a common ancestor
+ siblingCheck( ap[i], bp[i] ) :
+
+ // Otherwise nodes in our document sort first
+ ap[i] === preferredDoc ? -1 :
+ bp[i] === preferredDoc ? 1 :
+ 0;
+ };
+
+ return doc;
+};
+
+Sizzle.matches = function( expr, elements ) {
+ return Sizzle( expr, null, null, elements );
+};
+
+Sizzle.matchesSelector = function( elem, expr ) {
+ // Set document vars if needed
+ if ( ( elem.ownerDocument || elem ) !== document ) {
+ setDocument( elem );
+ }
+
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace( rattributeQuotes, "='$1']" );
+
+ if ( support.matchesSelector && documentIsHTML &&
+ ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
+ ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
+
+ try {
+ var ret = matches.call( elem, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || support.disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9
+ elem.document && elem.document.nodeType !== 11 ) {
+ return ret;
+ }
+ } catch (e) {}
+ }
+
+ return Sizzle( expr, document, null, [ elem ] ).length > 0;
+};
+
+Sizzle.contains = function( context, elem ) {
+ // Set document vars if needed
+ if ( ( context.ownerDocument || context ) !== document ) {
+ setDocument( context );
+ }
+ return contains( context, elem );
+};
+
+Sizzle.attr = function( elem, name ) {
+ // Set document vars if needed
+ if ( ( elem.ownerDocument || elem ) !== document ) {
+ setDocument( elem );
+ }
+
+ var fn = Expr.attrHandle[ name.toLowerCase() ],
+ // Don't get fooled by Object.prototype properties (jQuery #13807)
+ val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
+ fn( elem, name, !documentIsHTML ) :
+ undefined;
+
+ return val !== undefined ?
+ val :
+ support.attributes || !documentIsHTML ?
+ elem.getAttribute( name ) :
+ (val = elem.getAttributeNode(name)) && val.specified ?
+ val.value :
+ null;
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Document sorting and removing duplicates
+ * @param {ArrayLike} results
+ */
+Sizzle.uniqueSort = function( results ) {
+ var elem,
+ duplicates = [],
+ j = 0,
+ i = 0;
+
+ // Unless we *know* we can detect duplicates, assume their presence
+ hasDuplicate = !support.detectDuplicates;
+ sortInput = !support.sortStable && results.slice( 0 );
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ while ( (elem = results[i++]) ) {
+ if ( elem === results[ i ] ) {
+ j = duplicates.push( i );
+ }
+ }
+ while ( j-- ) {
+ results.splice( duplicates[ j ], 1 );
+ }
+ }
+
+ // Clear input after sorting to release objects
+ // See https://github.com/jquery/sizzle/pull/225
+ sortInput = null;
+
+ return results;
+};
+
+/**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+getText = Sizzle.getText = function( elem ) {
+ var node,
+ ret = "",
+ i = 0,
+ nodeType = elem.nodeType;
+
+ if ( !nodeType ) {
+ // If no nodeType, this is expected to be an array
+ while ( (node = elem[i++]) ) {
+ // Do not traverse comment nodes
+ ret += getText( node );
+ }
+ } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+ // Use textContent for elements
+ // innerText usage removed for consistency of new lines (jQuery #11153)
+ if ( typeof elem.textContent === "string" ) {
+ return elem.textContent;
+ } else {
+ // Traverse its children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ // Do not include comment or processing instruction nodes
+
+ return ret;
+};
+
+Expr = Sizzle.selectors = {
+
+ // Can be adjusted by the user
+ cacheLength: 50,
+
+ createPseudo: markFunction,
+
+ match: matchExpr,
+
+ attrHandle: {},
+
+ find: {},
+
+ relative: {
+ ">": { dir: "parentNode", first: true },
+ " ": { dir: "parentNode" },
+ "+": { dir: "previousSibling", first: true },
+ "~": { dir: "previousSibling" }
+ },
+
+ preFilter: {
+ "ATTR": function( match ) {
+ match[1] = match[1].replace( runescape, funescape );
+
+ // Move the given value to match[3] whether quoted or unquoted
+ match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );
+
+ if ( match[2] === "~=" ) {
+ match[3] = " " + match[3] + " ";
+ }
+
+ return match.slice( 0, 4 );
+ },
+
+ "CHILD": function( match ) {
+ /* matches from matchExpr["CHILD"]
+ 1 type (only|nth|...)
+ 2 what (child|of-type)
+ 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+ 4 xn-component of xn+y argument ([+-]?\d*n|)
+ 5 sign of xn-component
+ 6 x of xn-component
+ 7 sign of y-component
+ 8 y of y-component
+ */
+ match[1] = match[1].toLowerCase();
+
+ if ( match[1].slice( 0, 3 ) === "nth" ) {
+ // nth-* requires argument
+ if ( !match[3] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // numeric x and y parameters for Expr.filter.CHILD
+ // remember that false/true cast respectively to 0/1
+ match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
+ match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
+
+ // other types prohibit arguments
+ } else if ( match[3] ) {
+ Sizzle.error( match[0] );
+ }
+
+ return match;
+ },
+
+ "PSEUDO": function( match ) {
+ var excess,
+ unquoted = !match[6] && match[2];
+
+ if ( matchExpr["CHILD"].test( match[0] ) ) {
+ return null;
+ }
+
+ // Accept quoted arguments as-is
+ if ( match[3] ) {
+ match[2] = match[4] || match[5] || "";
+
+ // Strip excess characters from unquoted arguments
+ } else if ( unquoted && rpseudo.test( unquoted ) &&
+ // Get excess from tokenize (recursively)
+ (excess = tokenize( unquoted, true )) &&
+ // advance to the next closing parenthesis
+ (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
+
+ // excess is a negative index
+ match[0] = match[0].slice( 0, excess );
+ match[2] = unquoted.slice( 0, excess );
+ }
+
+ // Return only captures needed by the pseudo filter method (type and argument)
+ return match.slice( 0, 3 );
+ }
+ },
+
+ filter: {
+
+ "TAG": function( nodeNameSelector ) {
+ var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
+ return nodeNameSelector === "*" ?
+ function() { return true; } :
+ function( elem ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+ };
+ },
+
+ "CLASS": function( className ) {
+ var pattern = classCache[ className + " " ];
+
+ return pattern ||
+ (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
+ classCache( className, function( elem ) {
+ return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" );
+ });
+ },
+
+ "ATTR": function( name, operator, check ) {
+ return function( elem ) {
+ var result = Sizzle.attr( elem, name );
+
+ if ( result == null ) {
+ return operator === "!=";
+ }
+ if ( !operator ) {
+ return true;
+ }
+
+ result += "";
+
+ return operator === "=" ? result === check :
+ operator === "!=" ? result !== check :
+ operator === "^=" ? check && result.indexOf( check ) === 0 :
+ operator === "*=" ? check && result.indexOf( check ) > -1 :
+ operator === "$=" ? check && result.slice( -check.length ) === check :
+ operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
+ operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
+ false;
+ };
+ },
+
+ "CHILD": function( type, what, argument, first, last ) {
+ var simple = type.slice( 0, 3 ) !== "nth",
+ forward = type.slice( -4 ) !== "last",
+ ofType = what === "of-type";
+
+ return first === 1 && last === 0 ?
+
+ // Shortcut for :nth-*(n)
+ function( elem ) {
+ return !!elem.parentNode;
+ } :
+
+ function( elem, context, xml ) {
+ var cache, outerCache, node, diff, nodeIndex, start,
+ dir = simple !== forward ? "nextSibling" : "previousSibling",
+ parent = elem.parentNode,
+ name = ofType && elem.nodeName.toLowerCase(),
+ useCache = !xml && !ofType;
+
+ if ( parent ) {
+
+ // :(first|last|only)-(child|of-type)
+ if ( simple ) {
+ while ( dir ) {
+ node = elem;
+ while ( (node = node[ dir ]) ) {
+ if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
+ return false;
+ }
+ }
+ // Reverse direction for :only-* (if we haven't yet done so)
+ start = dir = type === "only" && !start && "nextSibling";
+ }
+ return true;
+ }
+
+ start = [ forward ? parent.firstChild : parent.lastChild ];
+
+ // non-xml :nth-child(...) stores cache data on `parent`
+ if ( forward && useCache ) {
+ // Seek `elem` from a previously-cached index
+ outerCache = parent[ expando ] || (parent[ expando ] = {});
+ cache = outerCache[ type ] || [];
+ nodeIndex = cache[0] === dirruns && cache[1];
+ diff = cache[0] === dirruns && cache[2];
+ node = nodeIndex && parent.childNodes[ nodeIndex ];
+
+ while ( (node = ++nodeIndex && node && node[ dir ] ||
+
+ // Fallback to seeking `elem` from the start
+ (diff = nodeIndex = 0) || start.pop()) ) {
+
+ // When found, cache indexes on `parent` and break
+ if ( node.nodeType === 1 && ++diff && node === elem ) {
+ outerCache[ type ] = [ dirruns, nodeIndex, diff ];
+ break;
+ }
+ }
+
+ // Use previously-cached element index if available
+ } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
+ diff = cache[1];
+
+ // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
+ } else {
+ // Use the same loop as above to seek `elem` from the start
+ while ( (node = ++nodeIndex && node && node[ dir ] ||
+ (diff = nodeIndex = 0) || start.pop()) ) {
+
+ if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
+ // Cache the index of each encountered element
+ if ( useCache ) {
+ (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
+ }
+
+ if ( node === elem ) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Incorporate the offset, then check against cycle size
+ diff -= last;
+ return diff === first || ( diff % first === 0 && diff / first >= 0 );
+ }
+ };
+ },
+
+ "PSEUDO": function( pseudo, argument ) {
+ // pseudo-class names are case-insensitive
+ // http://www.w3.org/TR/selectors/#pseudo-classes
+ // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+ // Remember that setFilters inherits from pseudos
+ var args,
+ fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+ Sizzle.error( "unsupported pseudo: " + pseudo );
+
+ // The user may use createPseudo to indicate that
+ // arguments are needed to create the filter function
+ // just as Sizzle does
+ if ( fn[ expando ] ) {
+ return fn( argument );
+ }
+
+ // But maintain support for old signatures
+ if ( fn.length > 1 ) {
+ args = [ pseudo, pseudo, "", argument ];
+ return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+ markFunction(function( seed, matches ) {
+ var idx,
+ matched = fn( seed, argument ),
+ i = matched.length;
+ while ( i-- ) {
+ idx = indexOf( seed, matched[i] );
+ seed[ idx ] = !( matches[ idx ] = matched[i] );
+ }
+ }) :
+ function( elem ) {
+ return fn( elem, 0, args );
+ };
+ }
+
+ return fn;
+ }
+ },
+
+ pseudos: {
+ // Potentially complex pseudos
+ "not": markFunction(function( selector ) {
+ // Trim the selector passed to compile
+ // to avoid treating leading and trailing
+ // spaces as combinators
+ var input = [],
+ results = [],
+ matcher = compile( selector.replace( rtrim, "$1" ) );
+
+ return matcher[ expando ] ?
+ markFunction(function( seed, matches, context, xml ) {
+ var elem,
+ unmatched = matcher( seed, null, xml, [] ),
+ i = seed.length;
+
+ // Match elements unmatched by `matcher`
+ while ( i-- ) {
+ if ( (elem = unmatched[i]) ) {
+ seed[i] = !(matches[i] = elem);
+ }
+ }
+ }) :
+ function( elem, context, xml ) {
+ input[0] = elem;
+ matcher( input, null, xml, results );
+ // Don't keep the element (issue #299)
+ input[0] = null;
+ return !results.pop();
+ };
+ }),
+
+ "has": markFunction(function( selector ) {
+ return function( elem ) {
+ return Sizzle( selector, elem ).length > 0;
+ };
+ }),
+
+ "contains": markFunction(function( text ) {
+ text = text.replace( runescape, funescape );
+ return function( elem ) {
+ return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+ };
+ }),
+
+ // "Whether an element is represented by a :lang() selector
+ // is based solely on the element's language value
+ // being equal to the identifier C,
+ // or beginning with the identifier C immediately followed by "-".
+ // The matching of C against the element's language value is performed case-insensitively.
+ // The identifier C does not have to be a valid language name."
+ // http://www.w3.org/TR/selectors/#lang-pseudo
+ "lang": markFunction( function( lang ) {
+ // lang value must be a valid identifier
+ if ( !ridentifier.test(lang || "") ) {
+ Sizzle.error( "unsupported lang: " + lang );
+ }
+ lang = lang.replace( runescape, funescape ).toLowerCase();
+ return function( elem ) {
+ var elemLang;
+ do {
+ if ( (elemLang = documentIsHTML ?
+ elem.lang :
+ elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
+
+ elemLang = elemLang.toLowerCase();
+ return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
+ }
+ } while ( (elem = elem.parentNode) && elem.nodeType === 1 );
+ return false;
+ };
+ }),
+
+ // Miscellaneous
+ "target": function( elem ) {
+ var hash = window.location && window.location.hash;
+ return hash && hash.slice( 1 ) === elem.id;
+ },
+
+ "root": function( elem ) {
+ return elem === docElem;
+ },
+
+ "focus": function( elem ) {
+ return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
+ },
+
+ // Boolean properties
+ "enabled": function( elem ) {
+ return elem.disabled === false;
+ },
+
+ "disabled": function( elem ) {
+ return elem.disabled === true;
+ },
+
+ "checked": function( elem ) {
+ // In CSS3, :checked should return both checked and selected elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ var nodeName = elem.nodeName.toLowerCase();
+ return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
+ },
+
+ "selected": function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ // Contents
+ "empty": function( elem ) {
+ // http://www.w3.org/TR/selectors/#empty-pseudo
+ // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
+ // but not by others (comment: 8; processing instruction: 7; etc.)
+ // nodeType < 6 works because attributes (2) do not appear as children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ if ( elem.nodeType < 6 ) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ "parent": function( elem ) {
+ return !Expr.pseudos["empty"]( elem );
+ },
+
+ // Element/input types
+ "header": function( elem ) {
+ return rheader.test( elem.nodeName );
+ },
+
+ "input": function( elem ) {
+ return rinputs.test( elem.nodeName );
+ },
+
+ "button": function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === "button" || name === "button";
+ },
+
+ "text": function( elem ) {
+ var attr;
+ return elem.nodeName.toLowerCase() === "input" &&
+ elem.type === "text" &&
+
+ // Support: IE<8
+ // New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
+ ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
+ },
+
+ // Position-in-collection
+ "first": createPositionalPseudo(function() {
+ return [ 0 ];
+ }),
+
+ "last": createPositionalPseudo(function( matchIndexes, length ) {
+ return [ length - 1 ];
+ }),
+
+ "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ return [ argument < 0 ? argument + length : argument ];
+ }),
+
+ "even": createPositionalPseudo(function( matchIndexes, length ) {
+ var i = 0;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "odd": createPositionalPseudo(function( matchIndexes, length ) {
+ var i = 1;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ var i = argument < 0 ? argument + length : argument;
+ for ( ; --i >= 0; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ var i = argument < 0 ? argument + length : argument;
+ for ( ; ++i < length; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ })
+ }
+};
+
+Expr.pseudos["nth"] = Expr.pseudos["eq"];
+
+// Add button/input type pseudos
+for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
+ Expr.pseudos[ i ] = createInputPseudo( i );
+}
+for ( i in { submit: true, reset: true } ) {
+ Expr.pseudos[ i ] = createButtonPseudo( i );
+}
+
+// Easy API for creating new setFilters
+function setFilters() {}
+setFilters.prototype = Expr.filters = Expr.pseudos;
+Expr.setFilters = new setFilters();
+
+tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
+ var matched, match, tokens, type,
+ soFar, groups, preFilters,
+ cached = tokenCache[ selector + " " ];
+
+ if ( cached ) {
+ return parseOnly ? 0 : cached.slice( 0 );
+ }
+
+ soFar = selector;
+ groups = [];
+ preFilters = Expr.preFilter;
+
+ while ( soFar ) {
+
+ // Comma and first run
+ if ( !matched || (match = rcomma.exec( soFar )) ) {
+ if ( match ) {
+ // Don't consume trailing commas as valid
+ soFar = soFar.slice( match[0].length ) || soFar;
+ }
+ groups.push( (tokens = []) );
+ }
+
+ matched = false;
+
+ // Combinators
+ if ( (match = rcombinators.exec( soFar )) ) {
+ matched = match.shift();
+ tokens.push({
+ value: matched,
+ // Cast descendant combinators to space
+ type: match[0].replace( rtrim, " " )
+ });
+ soFar = soFar.slice( matched.length );
+ }
+
+ // Filters
+ for ( type in Expr.filter ) {
+ if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
+ (match = preFilters[ type ]( match ))) ) {
+ matched = match.shift();
+ tokens.push({
+ value: matched,
+ type: type,
+ matches: match
+ });
+ soFar = soFar.slice( matched.length );
+ }
+ }
+
+ if ( !matched ) {
+ break;
+ }
+ }
+
+ // Return the length of the invalid excess
+ // if we're just parsing
+ // Otherwise, throw an error or return tokens
+ return parseOnly ?
+ soFar.length :
+ soFar ?
+ Sizzle.error( selector ) :
+ // Cache the tokens
+ tokenCache( selector, groups ).slice( 0 );
+};
+
+function toSelector( tokens ) {
+ var i = 0,
+ len = tokens.length,
+ selector = "";
+ for ( ; i < len; i++ ) {
+ selector += tokens[i].value;
+ }
+ return selector;
+}
+
+function addCombinator( matcher, combinator, base ) {
+ var dir = combinator.dir,
+ checkNonElements = base && dir === "parentNode",
+ doneName = done++;
+
+ return combinator.first ?
+ // Check against closest ancestor/preceding element
+ function( elem, context, xml ) {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ return matcher( elem, context, xml );
+ }
+ }
+ } :
+
+ // Check against all ancestor/preceding elements
+ function( elem, context, xml ) {
+ var oldCache, outerCache,
+ newCache = [ dirruns, doneName ];
+
+ // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
+ if ( xml ) {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ if ( matcher( elem, context, xml ) ) {
+ return true;
+ }
+ }
+ }
+ } else {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ outerCache = elem[ expando ] || (elem[ expando ] = {});
+ if ( (oldCache = outerCache[ dir ]) &&
+ oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
+
+ // Assign to newCache so results back-propagate to previous elements
+ return (newCache[ 2 ] = oldCache[ 2 ]);
+ } else {
+ // Reuse newcache so results back-propagate to previous elements
+ outerCache[ dir ] = newCache;
+
+ // A match means we're done; a fail means we have to keep checking
+ if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ };
+}
+
+function elementMatcher( matchers ) {
+ return matchers.length > 1 ?
+ function( elem, context, xml ) {
+ var i = matchers.length;
+ while ( i-- ) {
+ if ( !matchers[i]( elem, context, xml ) ) {
+ return false;
+ }
+ }
+ return true;
+ } :
+ matchers[0];
+}
+
+function multipleContexts( selector, contexts, results ) {
+ var i = 0,
+ len = contexts.length;
+ for ( ; i < len; i++ ) {
+ Sizzle( selector, contexts[i], results );
+ }
+ return results;
+}
+
+function condense( unmatched, map, filter, context, xml ) {
+ var elem,
+ newUnmatched = [],
+ i = 0,
+ len = unmatched.length,
+ mapped = map != null;
+
+ for ( ; i < len; i++ ) {
+ if ( (elem = unmatched[i]) ) {
+ if ( !filter || filter( elem, context, xml ) ) {
+ newUnmatched.push( elem );
+ if ( mapped ) {
+ map.push( i );
+ }
+ }
+ }
+ }
+
+ return newUnmatched;
+}
+
+function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+ if ( postFilter && !postFilter[ expando ] ) {
+ postFilter = setMatcher( postFilter );
+ }
+ if ( postFinder && !postFinder[ expando ] ) {
+ postFinder = setMatcher( postFinder, postSelector );
+ }
+ return markFunction(function( seed, results, context, xml ) {
+ var temp, i, elem,
+ preMap = [],
+ postMap = [],
+ preexisting = results.length,
+
+ // Get initial elements from seed or context
+ elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
+
+ // Prefilter to get matcher input, preserving a map for seed-results synchronization
+ matcherIn = preFilter && ( seed || !selector ) ?
+ condense( elems, preMap, preFilter, context, xml ) :
+ elems,
+
+ matcherOut = matcher ?
+ // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+ postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+ // ...intermediate processing is necessary
+ [] :
+
+ // ...otherwise use results directly
+ results :
+ matcherIn;
+
+ // Find primary matches
+ if ( matcher ) {
+ matcher( matcherIn, matcherOut, context, xml );
+ }
+
+ // Apply postFilter
+ if ( postFilter ) {
+ temp = condense( matcherOut, postMap );
+ postFilter( temp, [], context, xml );
+
+ // Un-match failing elements by moving them back to matcherIn
+ i = temp.length;
+ while ( i-- ) {
+ if ( (elem = temp[i]) ) {
+ matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
+ }
+ }
+ }
+
+ if ( seed ) {
+ if ( postFinder || preFilter ) {
+ if ( postFinder ) {
+ // Get the final matcherOut by condensing this intermediate into postFinder contexts
+ temp = [];
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) ) {
+ // Restore matcherIn since elem is not yet a final match
+ temp.push( (matcherIn[i] = elem) );
+ }
+ }
+ postFinder( null, (matcherOut = []), temp, xml );
+ }
+
+ // Move matched elements from seed to results to keep them synchronized
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) &&
+ (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {
+
+ seed[temp] = !(results[temp] = elem);
+ }
+ }
+ }
+
+ // Add elements to results, through postFinder if defined
+ } else {
+ matcherOut = condense(
+ matcherOut === results ?
+ matcherOut.splice( preexisting, matcherOut.length ) :
+ matcherOut
+ );
+ if ( postFinder ) {
+ postFinder( null, results, matcherOut, xml );
+ } else {
+ push.apply( results, matcherOut );
+ }
+ }
+ });
+}
+
+function matcherFromTokens( tokens ) {
+ var checkContext, matcher, j,
+ len = tokens.length,
+ leadingRelative = Expr.relative[ tokens[0].type ],
+ implicitRelative = leadingRelative || Expr.relative[" "],
+ i = leadingRelative ? 1 : 0,
+
+ // The foundational matcher ensures that elements are reachable from top-level context(s)
+ matchContext = addCombinator( function( elem ) {
+ return elem === checkContext;
+ }, implicitRelative, true ),
+ matchAnyContext = addCombinator( function( elem ) {
+ return indexOf( checkContext, elem ) > -1;
+ }, implicitRelative, true ),
+ matchers = [ function( elem, context, xml ) {
+ var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+ (checkContext = context).nodeType ?
+ matchContext( elem, context, xml ) :
+ matchAnyContext( elem, context, xml ) );
+ // Avoid hanging onto element (issue #299)
+ checkContext = null;
+ return ret;
+ } ];
+
+ for ( ; i < len; i++ ) {
+ if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
+ matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
+ } else {
+ matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
+
+ // Return special upon seeing a positional matcher
+ if ( matcher[ expando ] ) {
+ // Find the next relative operator (if any) for proper handling
+ j = ++i;
+ for ( ; j < len; j++ ) {
+ if ( Expr.relative[ tokens[j].type ] ) {
+ break;
+ }
+ }
+ return setMatcher(
+ i > 1 && elementMatcher( matchers ),
+ i > 1 && toSelector(
+ // If the preceding token was a descendant combinator, insert an implicit any-element `*`
+ tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
+ ).replace( rtrim, "$1" ),
+ matcher,
+ i < j && matcherFromTokens( tokens.slice( i, j ) ),
+ j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
+ j < len && toSelector( tokens )
+ );
+ }
+ matchers.push( matcher );
+ }
+ }
+
+ return elementMatcher( matchers );
+}
+
+function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+ var bySet = setMatchers.length > 0,
+ byElement = elementMatchers.length > 0,
+ superMatcher = function( seed, context, xml, results, outermost ) {
+ var elem, j, matcher,
+ matchedCount = 0,
+ i = "0",
+ unmatched = seed && [],
+ setMatched = [],
+ contextBackup = outermostContext,
+ // We must always have either seed elements or outermost context
+ elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
+ // Use integer dirruns iff this is the outermost matcher
+ dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
+ len = elems.length;
+
+ if ( outermost ) {
+ outermostContext = context !== document && context;
+ }
+
+ // Add elements passing elementMatchers directly to results
+ // Keep `i` a string if there are no elements so `matchedCount` will be "00" below
+ // Support: IE<9, Safari
+ // Tolerate NodeList properties (IE: "length"; Safari:
) matching elements by id
+ for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
+ if ( byElement && elem ) {
+ j = 0;
+ while ( (matcher = elementMatchers[j++]) ) {
+ if ( matcher( elem, context, xml ) ) {
+ results.push( elem );
+ break;
+ }
+ }
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ }
+ }
+
+ // Track unmatched elements for set filters
+ if ( bySet ) {
+ // They will have gone through all possible matchers
+ if ( (elem = !matcher && elem) ) {
+ matchedCount--;
+ }
+
+ // Lengthen the array for every element, matched or not
+ if ( seed ) {
+ unmatched.push( elem );
+ }
+ }
+ }
+
+ // Apply set filters to unmatched elements
+ matchedCount += i;
+ if ( bySet && i !== matchedCount ) {
+ j = 0;
+ while ( (matcher = setMatchers[j++]) ) {
+ matcher( unmatched, setMatched, context, xml );
+ }
+
+ if ( seed ) {
+ // Reintegrate element matches to eliminate the need for sorting
+ if ( matchedCount > 0 ) {
+ while ( i-- ) {
+ if ( !(unmatched[i] || setMatched[i]) ) {
+ setMatched[i] = pop.call( results );
+ }
+ }
+ }
+
+ // Discard index placeholder values to get only actual matches
+ setMatched = condense( setMatched );
+ }
+
+ // Add matches to results
+ push.apply( results, setMatched );
+
+ // Seedless set matches succeeding multiple successful matchers stipulate sorting
+ if ( outermost && !seed && setMatched.length > 0 &&
+ ( matchedCount + setMatchers.length ) > 1 ) {
+
+ Sizzle.uniqueSort( results );
+ }
+ }
+
+ // Override manipulation of globals by nested matchers
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ outermostContext = contextBackup;
+ }
+
+ return unmatched;
+ };
+
+ return bySet ?
+ markFunction( superMatcher ) :
+ superMatcher;
+}
+
+compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
+ var i,
+ setMatchers = [],
+ elementMatchers = [],
+ cached = compilerCache[ selector + " " ];
+
+ if ( !cached ) {
+ // Generate a function of recursive functions that can be used to check each element
+ if ( !match ) {
+ match = tokenize( selector );
+ }
+ i = match.length;
+ while ( i-- ) {
+ cached = matcherFromTokens( match[i] );
+ if ( cached[ expando ] ) {
+ setMatchers.push( cached );
+ } else {
+ elementMatchers.push( cached );
+ }
+ }
+
+ // Cache the compiled function
+ cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
+
+ // Save selector and tokenization
+ cached.selector = selector;
+ }
+ return cached;
+};
+
+/**
+ * A low-level selection function that works with Sizzle's compiled
+ * selector functions
+ * @param {String|Function} selector A selector or a pre-compiled
+ * selector function built with Sizzle.compile
+ * @param {Element} context
+ * @param {Array} [results]
+ * @param {Array} [seed] A set of elements to match against
+ */
+select = Sizzle.select = function( selector, context, results, seed ) {
+ var i, tokens, token, type, find,
+ compiled = typeof selector === "function" && selector,
+ match = !seed && tokenize( (selector = compiled.selector || selector) );
+
+ results = results || [];
+
+ // Try to minimize operations if there is no seed and only one group
+ if ( match.length === 1 ) {
+
+ // Take a shortcut and set the context if the root selector is an ID
+ tokens = match[0] = match[0].slice( 0 );
+ if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
+ support.getById && context.nodeType === 9 && documentIsHTML &&
+ Expr.relative[ tokens[1].type ] ) {
+
+ context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
+ if ( !context ) {
+ return results;
+
+ // Precompiled matchers will still verify ancestry, so step up a level
+ } else if ( compiled ) {
+ context = context.parentNode;
+ }
+
+ selector = selector.slice( tokens.shift().value.length );
+ }
+
+ // Fetch a seed set for right-to-left matching
+ i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
+ while ( i-- ) {
+ token = tokens[i];
+
+ // Abort if we hit a combinator
+ if ( Expr.relative[ (type = token.type) ] ) {
+ break;
+ }
+ if ( (find = Expr.find[ type ]) ) {
+ // Search, expanding context for leading sibling combinators
+ if ( (seed = find(
+ token.matches[0].replace( runescape, funescape ),
+ rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
+ )) ) {
+
+ // If seed is empty or no tokens remain, we can return early
+ tokens.splice( i, 1 );
+ selector = seed.length && toSelector( tokens );
+ if ( !selector ) {
+ push.apply( results, seed );
+ return results;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ // Compile and execute a filtering function if one is not provided
+ // Provide `match` to avoid retokenization if we modified the selector above
+ ( compiled || compile( selector, match ) )(
+ seed,
+ context,
+ !documentIsHTML,
+ results,
+ rsibling.test( selector ) && testContext( context.parentNode ) || context
+ );
+ return results;
+};
+
+// One-time assignments
+
+// Sort stability
+support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
+
+// Support: Chrome 14-35+
+// Always assume duplicates if they aren't passed to the comparison function
+support.detectDuplicates = !!hasDuplicate;
+
+// Initialize against the default document
+setDocument();
+
+// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
+// Detached nodes confoundingly follow *each other*
+support.sortDetached = assert(function( div1 ) {
+ // Should return 1, but returns 4 (following)
+ return div1.compareDocumentPosition( document.createElement("div") ) & 1;
+});
+
+// Support: IE<8
+// Prevent attribute/property "interpolation"
+// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+if ( !assert(function( div ) {
+ div.innerHTML = " ";
+ return div.firstChild.getAttribute("href") === "#" ;
+}) ) {
+ addHandle( "type|href|height|width", function( elem, name, isXML ) {
+ if ( !isXML ) {
+ return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
+ }
+ });
+}
+
+// Support: IE<9
+// Use defaultValue in place of getAttribute("value")
+if ( !support.attributes || !assert(function( div ) {
+ div.innerHTML = " ";
+ div.firstChild.setAttribute( "value", "" );
+ return div.firstChild.getAttribute( "value" ) === "";
+}) ) {
+ addHandle( "value", function( elem, name, isXML ) {
+ if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
+ return elem.defaultValue;
+ }
+ });
+}
+
+// Support: IE<9
+// Use getAttributeNode to fetch booleans when getAttribute lies
+if ( !assert(function( div ) {
+ return div.getAttribute("disabled") == null;
+}) ) {
+ addHandle( booleans, function( elem, name, isXML ) {
+ var val;
+ if ( !isXML ) {
+ return elem[ name ] === true ? name.toLowerCase() :
+ (val = elem.getAttributeNode( name )) && val.specified ?
+ val.value :
+ null;
+ }
+ });
+}
+
+return Sizzle;
+
+})( window );
+
+
+
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.pseudos;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+
+var rneedsContext = jQuery.expr.match.needsContext;
+
+var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/);
+
+
+
+var risSimple = /^.[^:#\[\.,]*$/;
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, not ) {
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep( elements, function( elem, i ) {
+ /* jshint -W018 */
+ return !!qualifier.call( elem, i, elem ) !== not;
+ });
+
+ }
+
+ if ( qualifier.nodeType ) {
+ return jQuery.grep( elements, function( elem ) {
+ return ( elem === qualifier ) !== not;
+ });
+
+ }
+
+ if ( typeof qualifier === "string" ) {
+ if ( risSimple.test( qualifier ) ) {
+ return jQuery.filter( qualifier, elements, not );
+ }
+
+ qualifier = jQuery.filter( qualifier, elements );
+ }
+
+ return jQuery.grep( elements, function( elem ) {
+ return ( indexOf.call( qualifier, elem ) >= 0 ) !== not;
+ });
+}
+
+jQuery.filter = function( expr, elems, not ) {
+ var elem = elems[ 0 ];
+
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return elems.length === 1 && elem.nodeType === 1 ?
+ jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
+ jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
+ return elem.nodeType === 1;
+ }));
+};
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var i,
+ len = this.length,
+ ret = [],
+ self = this;
+
+ if ( typeof selector !== "string" ) {
+ return this.pushStack( jQuery( selector ).filter(function() {
+ for ( i = 0; i < len; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ }) );
+ }
+
+ for ( i = 0; i < len; i++ ) {
+ jQuery.find( selector, self[ i ], ret );
+ }
+
+ // Needed because $( selector, context ) becomes $( context ).find( selector )
+ ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
+ ret.selector = this.selector ? this.selector + " " + selector : selector;
+ return ret;
+ },
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector || [], false) );
+ },
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector || [], true) );
+ },
+ is: function( selector ) {
+ return !!winnow(
+ this,
+
+ // If this is a positional/relative selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ typeof selector === "string" && rneedsContext.test( selector ) ?
+ jQuery( selector ) :
+ selector || [],
+ false
+ ).length;
+ }
+});
+
+
+// Initialize a jQuery object
+
+
+// A central reference to the root jQuery(document)
+var rootjQuery,
+
+ // A simple way to check for HTML strings
+ // Prioritize #id over to avoid XSS via location.hash (#9521)
+ // Strict HTML recognition (#11290: must start with <)
+ rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
+
+ init = jQuery.fn.init = function( selector, context ) {
+ var match, elem;
+
+ // HANDLE: $(""), $(null), $(undefined), $(false)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = rquickExpr.exec( selector );
+ }
+
+ // Match html or make sure no context is specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+
+ // Option to run scripts is true for back-compat
+ // Intentionally let the error be thrown if parseHTML is not present
+ jQuery.merge( this, jQuery.parseHTML(
+ match[1],
+ context && context.nodeType ? context.ownerDocument || context : document,
+ true
+ ) );
+
+ // HANDLE: $(html, props)
+ if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+ for ( match in context ) {
+ // Properties of context are called as methods if possible
+ if ( jQuery.isFunction( this[ match ] ) ) {
+ this[ match ]( context[ match ] );
+
+ // ...and otherwise set as attributes
+ } else {
+ this.attr( match, context[ match ] );
+ }
+ }
+ }
+
+ return this;
+
+ // HANDLE: $(#id)
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Support: Blackberry 4.6
+ // gEBID returns nodes no longer in the document (#6963)
+ if ( elem && elem.parentNode ) {
+ // Inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(DOMElement)
+ } else if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return typeof rootjQuery.ready !== "undefined" ?
+ rootjQuery.ready( selector ) :
+ // Execute immediately if ready is not present
+ selector( jQuery );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ };
+
+// Give the init function the jQuery prototype for later instantiation
+init.prototype = jQuery.fn;
+
+// Initialize central reference
+rootjQuery = jQuery( document );
+
+
+var rparentsprev = /^(?:parents|prev(?:Until|All))/,
+ // Methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.extend({
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ truncate = until !== undefined;
+
+ while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) {
+ if ( elem.nodeType === 1 ) {
+ if ( truncate && jQuery( elem ).is( until ) ) {
+ break;
+ }
+ matched.push( elem );
+ }
+ }
+ return matched;
+ },
+
+ sibling: function( n, elem ) {
+ var matched = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ matched.push( n );
+ }
+ }
+
+ return matched;
+ }
+});
+
+jQuery.fn.extend({
+ has: function( target ) {
+ var targets = jQuery( target, this ),
+ l = targets.length;
+
+ return this.filter(function() {
+ var i = 0;
+ for ( ; i < l; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ closest: function( selectors, context ) {
+ var cur,
+ i = 0,
+ l = this.length,
+ matched = [],
+ pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
+ jQuery( selectors, context || this.context ) :
+ 0;
+
+ for ( ; i < l; i++ ) {
+ for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {
+ // Always skip document fragments
+ if ( cur.nodeType < 11 && (pos ?
+ pos.index(cur) > -1 :
+
+ // Don't pass non-elements to Sizzle
+ cur.nodeType === 1 &&
+ jQuery.find.matchesSelector(cur, selectors)) ) {
+
+ matched.push( cur );
+ break;
+ }
+ }
+ }
+
+ return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched );
+ },
+
+ // Determine the position of an element within the set
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
+ }
+
+ // Index in selector
+ if ( typeof elem === "string" ) {
+ return indexOf.call( jQuery( elem ), this[ 0 ] );
+ }
+
+ // Locate the position of the desired element
+ return indexOf.call( this,
+
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[ 0 ] : elem
+ );
+ },
+
+ add: function( selector, context ) {
+ return this.pushStack(
+ jQuery.unique(
+ jQuery.merge( this.get(), jQuery( selector, context ) )
+ )
+ );
+ },
+
+ addBack: function( selector ) {
+ return this.add( selector == null ?
+ this.prevObject : this.prevObject.filter(selector)
+ );
+ }
+});
+
+function sibling( cur, dir ) {
+ while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {}
+ return cur;
+}
+
+jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return sibling( elem, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return sibling( elem, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return elem.contentDocument || jQuery.merge( [], elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var matched = jQuery.map( this, fn, until );
+
+ if ( name.slice( -5 ) !== "Until" ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ matched = jQuery.filter( selector, matched );
+ }
+
+ if ( this.length > 1 ) {
+ // Remove duplicates
+ if ( !guaranteedUnique[ name ] ) {
+ jQuery.unique( matched );
+ }
+
+ // Reverse order for parents* and prev-derivatives
+ if ( rparentsprev.test( name ) ) {
+ matched.reverse();
+ }
+ }
+
+ return this.pushStack( matched );
+ };
+});
+var rnotwhite = (/\S+/g);
+
+
+
+// String to Object options format cache
+var optionsCache = {};
+
+// Convert String-formatted options into Object-formatted ones and store in cache
+function createOptions( options ) {
+ var object = optionsCache[ options ] = {};
+ jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
+ object[ flag ] = true;
+ });
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * options: an optional list of space-separated options that will change how
+ * the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ ( optionsCache[ options ] || createOptions( options ) ) :
+ jQuery.extend( {}, options );
+
+ var // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list was already fired
+ fired,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = !options.once && [],
+ // Fire callbacks
+ fire = function( data ) {
+ memory = options.memory && data;
+ fired = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ firing = true;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+ memory = false; // To prevent further calls using add
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( stack ) {
+ if ( stack.length ) {
+ fire( stack.shift() );
+ }
+ } else if ( memory ) {
+ list = [];
+ } else {
+ self.disable();
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ // First, we save the current length
+ var start = list.length;
+ (function add( args ) {
+ jQuery.each( args, function( _, arg ) {
+ var type = jQuery.type( arg );
+ if ( type === "function" ) {
+ if ( !options.unique || !self.has( arg ) ) {
+ list.push( arg );
+ }
+ } else if ( arg && arg.length && type !== "string" ) {
+ // Inspect recursively
+ add( arg );
+ }
+ });
+ })( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away
+ } else if ( memory ) {
+ firingStart = start;
+ fire( memory );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ jQuery.each( arguments, function( _, arg ) {
+ var index;
+ while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+ // Handle firing indexes
+ if ( firing ) {
+ if ( index <= firingLength ) {
+ firingLength--;
+ }
+ if ( index <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ });
+ }
+ return this;
+ },
+ // Check if a given callback is in the list.
+ // If no argument is given, return whether or not list has callbacks attached.
+ has: function( fn ) {
+ return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ firingLength = 0;
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ if ( list && ( !fired || stack ) ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ if ( firing ) {
+ stack.push( args );
+ } else {
+ fire( args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+};
+
+
+jQuery.extend({
+
+ Deferred: function( func ) {
+ var tuples = [
+ // action, add listener, listener list, final state
+ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
+ [ "notify", "progress", jQuery.Callbacks("memory") ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
+ },
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
+ },
+ then: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( tuples, function( i, tuple ) {
+ var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
+ // deferred[ done | fail | progress ] for forwarding actions to newDefer
+ deferred[ tuple[1] ](function() {
+ var returned = fn && fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise()
+ .done( newDefer.resolve )
+ .fail( newDefer.reject )
+ .progress( newDefer.notify );
+ } else {
+ newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
+ }
+ });
+ });
+ fns = null;
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return obj != null ? jQuery.extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
+
+ // Keep pipe for back-compat
+ promise.pipe = promise.then;
+
+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 3 ];
+
+ // promise[ done | fail | progress ] = list.add
+ promise[ tuple[1] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(function() {
+ // state = [ resolved | rejected ]
+ state = stateString;
+
+ // [ reject_list | resolve_list ].disable; progress_list.lock
+ }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+ }
+
+ // deferred[ resolve | reject | notify ]
+ deferred[ tuple[0] ] = function() {
+ deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
+ return this;
+ };
+ deferred[ tuple[0] + "With" ] = list.fireWith;
+ });
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( subordinate /* , ..., subordinateN */ ) {
+ var i = 0,
+ resolveValues = slice.call( arguments ),
+ length = resolveValues.length,
+
+ // the count of uncompleted subordinates
+ remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+ // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+ deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+ // Update function for both resolve and progress values
+ updateFunc = function( i, contexts, values ) {
+ return function( value ) {
+ contexts[ i ] = this;
+ values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
+ if ( values === progressValues ) {
+ deferred.notifyWith( contexts, values );
+ } else if ( !( --remaining ) ) {
+ deferred.resolveWith( contexts, values );
+ }
+ };
+ },
+
+ progressValues, progressContexts, resolveContexts;
+
+ // Add listeners to Deferred subordinates; treat others as resolved
+ if ( length > 1 ) {
+ progressValues = new Array( length );
+ progressContexts = new Array( length );
+ resolveContexts = new Array( length );
+ for ( ; i < length; i++ ) {
+ if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+ resolveValues[ i ].promise()
+ .done( updateFunc( i, resolveContexts, resolveValues ) )
+ .fail( deferred.reject )
+ .progress( updateFunc( i, progressContexts, progressValues ) );
+ } else {
+ --remaining;
+ }
+ }
+ }
+
+ // If we're not waiting on anything, resolve the master
+ if ( !remaining ) {
+ deferred.resolveWith( resolveContexts, resolveValues );
+ }
+
+ return deferred.promise();
+ }
+});
+
+
+// The deferred used on DOM ready
+var readyList;
+
+jQuery.fn.ready = function( fn ) {
+ // Add the callback
+ jQuery.ready.promise().done( fn );
+
+ return this;
+};
+
+jQuery.extend({
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+
+ // Abort if there are pending holds or we're already ready
+ if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+ return;
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.triggerHandler ) {
+ jQuery( document ).triggerHandler( "ready" );
+ jQuery( document ).off( "ready" );
+ }
+ }
+});
+
+/**
+ * The ready event handler and self cleanup method
+ */
+function completed() {
+ document.removeEventListener( "DOMContentLoaded", completed, false );
+ window.removeEventListener( "load", completed, false );
+ jQuery.ready();
+}
+
+jQuery.ready.promise = function( obj ) {
+ if ( !readyList ) {
+
+ readyList = jQuery.Deferred();
+
+ // Catch cases where $(document).ready() is called after the browser event has already occurred.
+ // We once tried to use readyState "interactive" here, but it caused issues like the one
+ // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ setTimeout( jQuery.ready );
+
+ } else {
+
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", completed, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", completed, false );
+ }
+ }
+ return readyList.promise( obj );
+};
+
+// Kick off the DOM ready check even if the user does not
+jQuery.ready.promise();
+
+
+
+
+// Multifunctional method to get and set values of a collection
+// The value/s can optionally be executed if it's a function
+var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
+ var i = 0,
+ len = elems.length,
+ bulk = key == null;
+
+ // Sets many values
+ if ( jQuery.type( key ) === "object" ) {
+ chainable = true;
+ for ( i in key ) {
+ jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
+ }
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ chainable = true;
+
+ if ( !jQuery.isFunction( value ) ) {
+ raw = true;
+ }
+
+ if ( bulk ) {
+ // Bulk operations run against the entire set
+ if ( raw ) {
+ fn.call( elems, value );
+ fn = null;
+
+ // ...except when executing function values
+ } else {
+ bulk = fn;
+ fn = function( elem, key, value ) {
+ return bulk.call( jQuery( elem ), value );
+ };
+ }
+ }
+
+ if ( fn ) {
+ for ( ; i < len; i++ ) {
+ fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
+ }
+ }
+ }
+
+ return chainable ?
+ elems :
+
+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ len ? fn( elems[0], key ) : emptyGet;
+};
+
+
+/**
+ * Determines whether an object can have data
+ */
+jQuery.acceptData = function( owner ) {
+ // Accepts only:
+ // - Node
+ // - Node.ELEMENT_NODE
+ // - Node.DOCUMENT_NODE
+ // - Object
+ // - Any
+ /* jshint -W018 */
+ return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
+};
+
+
+function Data() {
+ // Support: Android<4,
+ // Old WebKit does not have Object.preventExtensions/freeze method,
+ // return new empty object instead with no [[set]] accessor
+ Object.defineProperty( this.cache = {}, 0, {
+ get: function() {
+ return {};
+ }
+ });
+
+ this.expando = jQuery.expando + Data.uid++;
+}
+
+Data.uid = 1;
+Data.accepts = jQuery.acceptData;
+
+Data.prototype = {
+ key: function( owner ) {
+ // We can accept data for non-element nodes in modern browsers,
+ // but we should not, see #8335.
+ // Always return the key for a frozen object.
+ if ( !Data.accepts( owner ) ) {
+ return 0;
+ }
+
+ var descriptor = {},
+ // Check if the owner object already has a cache key
+ unlock = owner[ this.expando ];
+
+ // If not, create one
+ if ( !unlock ) {
+ unlock = Data.uid++;
+
+ // Secure it in a non-enumerable, non-writable property
+ try {
+ descriptor[ this.expando ] = { value: unlock };
+ Object.defineProperties( owner, descriptor );
+
+ // Support: Android<4
+ // Fallback to a less secure definition
+ } catch ( e ) {
+ descriptor[ this.expando ] = unlock;
+ jQuery.extend( owner, descriptor );
+ }
+ }
+
+ // Ensure the cache object
+ if ( !this.cache[ unlock ] ) {
+ this.cache[ unlock ] = {};
+ }
+
+ return unlock;
+ },
+ set: function( owner, data, value ) {
+ var prop,
+ // There may be an unlock assigned to this node,
+ // if there is no entry for this "owner", create one inline
+ // and set the unlock as though an owner entry had always existed
+ unlock = this.key( owner ),
+ cache = this.cache[ unlock ];
+
+ // Handle: [ owner, key, value ] args
+ if ( typeof data === "string" ) {
+ cache[ data ] = value;
+
+ // Handle: [ owner, { properties } ] args
+ } else {
+ // Fresh assignments by object are shallow copied
+ if ( jQuery.isEmptyObject( cache ) ) {
+ jQuery.extend( this.cache[ unlock ], data );
+ // Otherwise, copy the properties one-by-one to the cache object
+ } else {
+ for ( prop in data ) {
+ cache[ prop ] = data[ prop ];
+ }
+ }
+ }
+ return cache;
+ },
+ get: function( owner, key ) {
+ // Either a valid cache is found, or will be created.
+ // New caches will be created and the unlock returned,
+ // allowing direct access to the newly created
+ // empty data object. A valid owner object must be provided.
+ var cache = this.cache[ this.key( owner ) ];
+
+ return key === undefined ?
+ cache : cache[ key ];
+ },
+ access: function( owner, key, value ) {
+ var stored;
+ // In cases where either:
+ //
+ // 1. No key was specified
+ // 2. A string key was specified, but no value provided
+ //
+ // Take the "read" path and allow the get method to determine
+ // which value to return, respectively either:
+ //
+ // 1. The entire cache object
+ // 2. The data stored at the key
+ //
+ if ( key === undefined ||
+ ((key && typeof key === "string") && value === undefined) ) {
+
+ stored = this.get( owner, key );
+
+ return stored !== undefined ?
+ stored : this.get( owner, jQuery.camelCase(key) );
+ }
+
+ // [*]When the key is not a string, or both a key and value
+ // are specified, set or extend (existing objects) with either:
+ //
+ // 1. An object of properties
+ // 2. A key and value
+ //
+ this.set( owner, key, value );
+
+ // Since the "set" path can have two possible entry points
+ // return the expected data based on which path was taken[*]
+ return value !== undefined ? value : key;
+ },
+ remove: function( owner, key ) {
+ var i, name, camel,
+ unlock = this.key( owner ),
+ cache = this.cache[ unlock ];
+
+ if ( key === undefined ) {
+ this.cache[ unlock ] = {};
+
+ } else {
+ // Support array or space separated string of keys
+ if ( jQuery.isArray( key ) ) {
+ // If "name" is an array of keys...
+ // When data is initially created, via ("key", "val") signature,
+ // keys will be converted to camelCase.
+ // Since there is no way to tell _how_ a key was added, remove
+ // both plain key and camelCase key. #12786
+ // This will only penalize the array argument path.
+ name = key.concat( key.map( jQuery.camelCase ) );
+ } else {
+ camel = jQuery.camelCase( key );
+ // Try the string as a key before any manipulation
+ if ( key in cache ) {
+ name = [ key, camel ];
+ } else {
+ // If a key with the spaces exists, use it.
+ // Otherwise, create an array by matching non-whitespace
+ name = camel;
+ name = name in cache ?
+ [ name ] : ( name.match( rnotwhite ) || [] );
+ }
+ }
+
+ i = name.length;
+ while ( i-- ) {
+ delete cache[ name[ i ] ];
+ }
+ }
+ },
+ hasData: function( owner ) {
+ return !jQuery.isEmptyObject(
+ this.cache[ owner[ this.expando ] ] || {}
+ );
+ },
+ discard: function( owner ) {
+ if ( owner[ this.expando ] ) {
+ delete this.cache[ owner[ this.expando ] ];
+ }
+ }
+};
+var data_priv = new Data();
+
+var data_user = new Data();
+
+
+
+// Implementation Summary
+//
+// 1. Enforce API surface and semantic compatibility with 1.9.x branch
+// 2. Improve the module's maintainability by reducing the storage
+// paths to a single mechanism.
+// 3. Use the same single mechanism to support "private" and "user" data.
+// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
+// 5. Avoid exposing implementation details on user objects (eg. expando properties)
+// 6. Provide a clear path for implementation upgrade to WeakMap in 2014
+
+var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+function dataAttr( elem, key, data ) {
+ var name;
+
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+ name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ // Only convert to a number if it doesn't change the string
+ +data + "" === data ? +data :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ data_user.set( elem, key, data );
+ } else {
+ data = undefined;
+ }
+ }
+ return data;
+}
+
+jQuery.extend({
+ hasData: function( elem ) {
+ return data_user.hasData( elem ) || data_priv.hasData( elem );
+ },
+
+ data: function( elem, name, data ) {
+ return data_user.access( elem, name, data );
+ },
+
+ removeData: function( elem, name ) {
+ data_user.remove( elem, name );
+ },
+
+ // TODO: Now that all calls to _data and _removeData have been replaced
+ // with direct calls to data_priv methods, these can be deprecated.
+ _data: function( elem, name, data ) {
+ return data_priv.access( elem, name, data );
+ },
+
+ _removeData: function( elem, name ) {
+ data_priv.remove( elem, name );
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var i, name, data,
+ elem = this[ 0 ],
+ attrs = elem && elem.attributes;
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = data_user.get( elem );
+
+ if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) {
+ i = attrs.length;
+ while ( i-- ) {
+
+ // Support: IE11+
+ // The attrs elements can be null (#14894)
+ if ( attrs[ i ] ) {
+ name = attrs[ i ].name;
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = jQuery.camelCase( name.slice(5) );
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ }
+ data_priv.set( elem, "hasDataAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each(function() {
+ data_user.set( this, key );
+ });
+ }
+
+ return access( this, function( value ) {
+ var data,
+ camelKey = jQuery.camelCase( key );
+
+ // The calling jQuery object (element matches) is not empty
+ // (and therefore has an element appears at this[ 0 ]) and the
+ // `value` parameter was not undefined. An empty jQuery object
+ // will result in `undefined` for elem = this[ 0 ] which will
+ // throw an exception if an attempt to read a data cache is made.
+ if ( elem && value === undefined ) {
+ // Attempt to get data from the cache
+ // with the key as-is
+ data = data_user.get( elem, key );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // Attempt to get data from the cache
+ // with the key camelized
+ data = data_user.get( elem, camelKey );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // Attempt to "discover" the data in
+ // HTML5 custom data-* attrs
+ data = dataAttr( elem, camelKey, undefined );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // We tried really hard, but the data doesn't exist.
+ return;
+ }
+
+ // Set the data...
+ this.each(function() {
+ // First, attempt to store a copy or reference of any
+ // data that might've been store with a camelCased key.
+ var data = data_user.get( this, camelKey );
+
+ // For HTML5 data-* attribute interop, we have to
+ // store property names with dashes in a camelCase form.
+ // This might not apply to all properties...*
+ data_user.set( this, camelKey, value );
+
+ // *... In the case of properties that might _actually_
+ // have dashes, we need to also store a copy of that
+ // unchanged property.
+ if ( key.indexOf("-") !== -1 && data !== undefined ) {
+ data_user.set( this, key, value );
+ }
+ });
+ }, null, value, arguments.length > 1, null, true );
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ data_user.remove( this, key );
+ });
+ }
+});
+
+
+jQuery.extend({
+ queue: function( elem, type, data ) {
+ var queue;
+
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = data_priv.get( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !queue || jQuery.isArray( data ) ) {
+ queue = data_priv.access( elem, type, jQuery.makeArray(data) );
+ } else {
+ queue.push( data );
+ }
+ }
+ return queue || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ startLength = queue.length,
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ startLength--;
+ }
+
+ if ( fn ) {
+
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ // Clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
+
+ if ( !startLength && hooks ) {
+ hooks.empty.fire();
+ }
+ },
+
+ // Not public - generate a queueHooks object, or return the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return data_priv.get( elem, key ) || data_priv.access( elem, key, {
+ empty: jQuery.Callbacks("once memory").add(function() {
+ data_priv.remove( elem, [ type + "queue", key ] );
+ })
+ });
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[0], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ // Ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, obj ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
+
+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
+
+ while ( i-- ) {
+ tmp = data_priv.get( elements[ i ], type + "queueHooks" );
+ if ( tmp && tmp.empty ) {
+ count++;
+ tmp.empty.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( obj );
+ }
+});
+var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source;
+
+var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
+
+var isHidden = function( elem, el ) {
+ // isHidden might be called from jQuery#filter function;
+ // in that case, element will be second argument
+ elem = el || elem;
+ return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
+ };
+
+var rcheckableType = (/^(?:checkbox|radio)$/i);
+
+
+
+(function() {
+ var fragment = document.createDocumentFragment(),
+ div = fragment.appendChild( document.createElement( "div" ) ),
+ input = document.createElement( "input" );
+
+ // Support: Safari<=5.1
+ // Check state lost if the name is set (#11217)
+ // Support: Windows Web Apps (WWA)
+ // `name` and `type` must use .setAttribute for WWA (#14901)
+ input.setAttribute( "type", "radio" );
+ input.setAttribute( "checked", "checked" );
+ input.setAttribute( "name", "t" );
+
+ div.appendChild( input );
+
+ // Support: Safari<=5.1, Android<4.2
+ // Older WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Support: IE<=11+
+ // Make sure textarea (and checkbox) defaultValue is properly cloned
+ div.innerHTML = "";
+ support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
+})();
+var strundefined = typeof undefined;
+
+
+
+support.focusinBubbles = "onfocusin" in window;
+
+
+var
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
+
+function returnTrue() {
+ return true;
+}
+
+function returnFalse() {
+ return false;
+}
+
+function safeActiveElement() {
+ try {
+ return document.activeElement;
+ } catch ( err ) { }
+}
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ global: {},
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var handleObjIn, eventHandle, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = data_priv.get( elem );
+
+ // Don't attach events to noData or text/comment nodes (but allow plain objects)
+ if ( !elemData ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ if ( !(events = elemData.events) ) {
+ events = elemData.events = {};
+ }
+ if ( !(eventHandle = elemData.handle) ) {
+ eventHandle = elemData.handle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?
+ jQuery.event.dispatch.apply( elem, arguments ) : undefined;
+ };
+ }
+
+ // Handle multiple events separated by a space
+ types = ( types || "" ).match( rnotwhite ) || [ "" ];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tmp[1];
+ namespaces = ( tmp[2] || "" ).split( "." ).sort();
+
+ // There *must* be a type, no attaching namespace-only handlers
+ if ( !type ) {
+ continue;
+ }
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: origType,
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+ namespace: namespaces.join(".")
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ if ( !(handlers = events[ type ]) ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ },
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var j, origCount, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = data_priv.hasData( elem ) && data_priv.get( elem );
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = ( types || "" ).match( rnotwhite ) || [ "" ];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tmp[1];
+ namespaces = ( tmp[2] || "" ).split( "." ).sort();
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+ handlers = events[ type ] || [];
+ tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
+
+ // Remove matching events
+ origCount = j = handlers.length;
+ while ( j-- ) {
+ handleObj = handlers[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !tmp || tmp.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ handlers.splice( j, 1 );
+
+ if ( handleObj.selector ) {
+ handlers.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( origCount && !handlers.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ delete elemData.handle;
+ data_priv.remove( elem, "events" );
+ }
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+
+ var i, cur, tmp, bubbleType, ontype, handle, special,
+ eventPath = [ elem || document ],
+ type = hasOwn.call( event, "type" ) ? event.type : event,
+ namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
+
+ cur = tmp = elem = elem || document;
+
+ // Don't do events on text and comment nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf(".") >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+ ontype = type.indexOf(":") < 0 && "on" + type;
+
+ // Caller can pass in a jQuery.Event object, Object, or just an event type string
+ event = event[ jQuery.expando ] ?
+ event :
+ new jQuery.Event( type, typeof event === "object" && event );
+
+ // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
+ event.isTrigger = onlyHandlers ? 2 : 3;
+ event.namespace = namespaces.join(".");
+ event.namespace_re = event.namespace ?
+ new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
+ null;
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data == null ?
+ [ event ] :
+ jQuery.makeArray( data, [ event ] );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ if ( !rfocusMorph.test( bubbleType + type ) ) {
+ cur = cur.parentNode;
+ }
+ for ( ; cur; cur = cur.parentNode ) {
+ eventPath.push( cur );
+ tmp = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( tmp === (elem.ownerDocument || document) ) {
+ eventPath.push( tmp.defaultView || tmp.parentWindow || window );
+ }
+ }
+
+ // Fire handlers on the event path
+ i = 0;
+ while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
+
+ event.type = i > 1 ?
+ bubbleType :
+ special.bindType || type;
+
+ // jQuery handler
+ handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+
+ // Native handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && handle.apply && jQuery.acceptData( cur ) ) {
+ event.result = handle.apply( cur, data );
+ if ( event.result === false ) {
+ event.preventDefault();
+ }
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
+ jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ tmp = elem[ ontype ];
+
+ if ( tmp ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ elem[ type ]();
+ jQuery.event.triggered = undefined;
+
+ if ( tmp ) {
+ elem[ ontype ] = tmp;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event );
+
+ var i, j, ret, matched, handleObj,
+ handlerQueue = [],
+ args = slice.call( arguments ),
+ handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [],
+ special = jQuery.event.special[ event.type ] || {};
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers
+ handlerQueue = jQuery.event.handlers.call( this, event, handlers );
+
+ // Run delegates first; they may want to stop propagation beneath us
+ i = 0;
+ while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
+ event.currentTarget = matched.elem;
+
+ j = 0;
+ while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
+
+ // Triggered event must either 1) have no namespace, or 2) have namespace(s)
+ // a subset or equal to those in the bound event (both can have no namespace).
+ if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.handleObj = handleObj;
+ event.data = handleObj.data;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ if ( (event.result = ret) === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ handlers: function( event, handlers ) {
+ var i, matches, sel, handleObj,
+ handlerQueue = [],
+ delegateCount = handlers.delegateCount,
+ cur = event.target;
+
+ // Find delegate handlers
+ // Black-hole SVG instance trees (#13180)
+ // Avoid non-left-click bubbling in Firefox (#3861)
+ if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {
+
+ for ( ; cur !== this; cur = cur.parentNode || this ) {
+
+ // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
+ if ( cur.disabled !== true || event.type !== "click" ) {
+ matches = [];
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+
+ // Don't conflict with Object.prototype properties (#13203)
+ sel = handleObj.selector + " ";
+
+ if ( matches[ sel ] === undefined ) {
+ matches[ sel ] = handleObj.needsContext ?
+ jQuery( sel, this ).index( cur ) >= 0 :
+ jQuery.find( sel, this, null, [ cur ] ).length;
+ }
+ if ( matches[ sel ] ) {
+ matches.push( handleObj );
+ }
+ }
+ if ( matches.length ) {
+ handlerQueue.push({ elem: cur, handlers: matches });
+ }
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ if ( delegateCount < handlers.length ) {
+ handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
+ }
+
+ return handlerQueue;
+ },
+
+ // Includes some event props shared by KeyEvent and MouseEvent
+ props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+ fixHooks: {},
+
+ keyHooks: {
+ props: "char charCode key keyCode".split(" "),
+ filter: function( event, original ) {
+
+ // Add which for key events
+ if ( event.which == null ) {
+ event.which = original.charCode != null ? original.charCode : original.keyCode;
+ }
+
+ return event;
+ }
+ },
+
+ mouseHooks: {
+ props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+ filter: function( event, original ) {
+ var eventDoc, doc, body,
+ button = original.button;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && original.clientX != null ) {
+ eventDoc = event.target.ownerDocument || document;
+ doc = eventDoc.documentElement;
+ body = eventDoc.body;
+
+ event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+ event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && button !== undefined ) {
+ event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+ }
+
+ return event;
+ }
+ },
+
+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
+
+ // Create a writable copy of the event object and normalize some properties
+ var i, prop, copy,
+ type = event.type,
+ originalEvent = event,
+ fixHook = this.fixHooks[ type ];
+
+ if ( !fixHook ) {
+ this.fixHooks[ type ] = fixHook =
+ rmouseEvent.test( type ) ? this.mouseHooks :
+ rkeyEvent.test( type ) ? this.keyHooks :
+ {};
+ }
+ copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+ event = new jQuery.Event( originalEvent );
+
+ i = copy.length;
+ while ( i-- ) {
+ prop = copy[ i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Support: Cordova 2.5 (WebKit) (#13255)
+ // All events should have a target; Cordova deviceready doesn't
+ if ( !event.target ) {
+ event.target = document;
+ }
+
+ // Support: Safari 6.0+, Chrome<28
+ // Target should not be a text node (#504, #13143)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
+ },
+
+ special: {
+ load: {
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+ focus: {
+ // Fire native event if possible so blur/focus sequence is correct
+ trigger: function() {
+ if ( this !== safeActiveElement() && this.focus ) {
+ this.focus();
+ return false;
+ }
+ },
+ delegateType: "focusin"
+ },
+ blur: {
+ trigger: function() {
+ if ( this === safeActiveElement() && this.blur ) {
+ this.blur();
+ return false;
+ }
+ },
+ delegateType: "focusout"
+ },
+ click: {
+ // For checkbox, fire native event so checked state will be right
+ trigger: function() {
+ if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) {
+ this.click();
+ return false;
+ }
+ },
+
+ // For cross-browser consistency, don't fire native .click() on links
+ _default: function( event ) {
+ return jQuery.nodeName( event.target, "a" );
+ }
+ },
+
+ beforeunload: {
+ postDispatch: function( event ) {
+
+ // Support: Firefox 20+
+ // Firefox doesn't alert if the returnValue field is not set.
+ if ( event.result !== undefined && event.originalEvent ) {
+ event.originalEvent.returnValue = event.result;
+ }
+ }
+ }
+ },
+
+ simulate: function( type, elem, event, bubble ) {
+ // Piggyback on a donor event to simulate a different one.
+ // Fake originalEvent to avoid donor's stopPropagation, but if the
+ // simulated event prevents default then we do the same on the donor.
+ var e = jQuery.extend(
+ new jQuery.Event(),
+ event,
+ {
+ type: type,
+ isSimulated: true,
+ originalEvent: {}
+ }
+ );
+ if ( bubble ) {
+ jQuery.event.trigger( e, null, elem );
+ } else {
+ jQuery.event.dispatch.call( elem, e );
+ }
+ if ( e.isDefaultPrevented() ) {
+ event.preventDefault();
+ }
+ }
+};
+
+jQuery.removeEvent = function( elem, type, handle ) {
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle, false );
+ }
+};
+
+jQuery.Event = function( src, props ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !(this instanceof jQuery.Event) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = src.defaultPrevented ||
+ src.defaultPrevented === undefined &&
+ // Support: Android<4.0
+ src.returnValue === false ?
+ returnTrue :
+ returnFalse;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || jQuery.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse,
+
+ preventDefault: function() {
+ var e = this.originalEvent;
+
+ this.isDefaultPrevented = returnTrue;
+
+ if ( e && e.preventDefault ) {
+ e.preventDefault();
+ }
+ },
+ stopPropagation: function() {
+ var e = this.originalEvent;
+
+ this.isPropagationStopped = returnTrue;
+
+ if ( e && e.stopPropagation ) {
+ e.stopPropagation();
+ }
+ },
+ stopImmediatePropagation: function() {
+ var e = this.originalEvent;
+
+ this.isImmediatePropagationStopped = returnTrue;
+
+ if ( e && e.stopImmediatePropagation ) {
+ e.stopImmediatePropagation();
+ }
+
+ this.stopPropagation();
+ }
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+// Support: Chrome 15+
+jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout",
+ pointerenter: "pointerover",
+ pointerleave: "pointerout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
+
+ handle: function( event ) {
+ var ret,
+ target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj;
+
+ // For mousenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
+ };
+});
+
+// Support: Firefox, Chrome, Safari
+// Create "bubbling" focus and blur events
+if ( !support.focusinBubbles ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // Attach a single capturing handler on the document while someone wants focusin/focusout
+ var handler = function( event ) {
+ jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+ };
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ var doc = this.ownerDocument || this,
+ attaches = data_priv.access( doc, fix );
+
+ if ( !attaches ) {
+ doc.addEventListener( orig, handler, true );
+ }
+ data_priv.access( doc, fix, ( attaches || 0 ) + 1 );
+ },
+ teardown: function() {
+ var doc = this.ownerDocument || this,
+ attaches = data_priv.access( doc, fix ) - 1;
+
+ if ( !attaches ) {
+ doc.removeEventListener( orig, handler, true );
+ data_priv.remove( doc, fix );
+
+ } else {
+ data_priv.access( doc, fix, attaches );
+ }
+ }
+ };
+ });
+}
+
+jQuery.fn.extend({
+
+ on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+ var origFn, type;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) {
+ // ( types-Object, data )
+ data = data || selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ this.on( type, selector, data, types[ type ], one );
+ }
+ return this;
+ }
+
+ if ( data == null && fn == null ) {
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return this;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return this.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ });
+ },
+ one: function( types, selector, data, fn ) {
+ return this.on( types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ var handleObj, type;
+ if ( types && types.preventDefault && types.handleObj ) {
+ // ( event ) dispatched jQuery.Event
+ handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+ // ( types-object [, selector] )
+ for ( type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each(function() {
+ jQuery.event.remove( this, types, fn, selector );
+ });
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+ triggerHandler: function( type, data ) {
+ var elem = this[0];
+ if ( elem ) {
+ return jQuery.event.trigger( type, data, elem, true );
+ }
+ }
+});
+
+
+var
+ rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
+ rtagName = /<([\w:]+)/,
+ rhtml = /<|?\w+;/,
+ rnoInnerhtml = /<(?:script|style|link)/i,
+ // checked="checked" or checked
+ rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+ rscriptType = /^$|\/(?:java|ecma)script/i,
+ rscriptTypeMasked = /^true\/(.*)/,
+ rcleanScript = /^\s*\s*$/g,
+
+ // We have to close these tags to support XHTML (#13200)
+ wrapMap = {
+
+ // Support: IE9
+ option: [ 1, "", " " ],
+
+ thead: [ 1, "" ],
+ col: [ 2, "" ],
+ tr: [ 2, "" ],
+ td: [ 3, "" ],
+
+ _default: [ 0, "", "" ]
+ };
+
+// Support: IE9
+wrapMap.optgroup = wrapMap.option;
+
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// Support: 1.x compatibility
+// Manipulating tables requires a tbody
+function manipulationTarget( elem, content ) {
+ return jQuery.nodeName( elem, "table" ) &&
+ jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ?
+
+ elem.getElementsByTagName("tbody")[0] ||
+ elem.appendChild( elem.ownerDocument.createElement("tbody") ) :
+ elem;
+}
+
+// Replace/restore the type attribute of script elements for safe DOM manipulation
+function disableScript( elem ) {
+ elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type;
+ return elem;
+}
+function restoreScript( elem ) {
+ var match = rscriptTypeMasked.exec( elem.type );
+
+ if ( match ) {
+ elem.type = match[ 1 ];
+ } else {
+ elem.removeAttribute("type");
+ }
+
+ return elem;
+}
+
+// Mark scripts as having already been evaluated
+function setGlobalEval( elems, refElements ) {
+ var i = 0,
+ l = elems.length;
+
+ for ( ; i < l; i++ ) {
+ data_priv.set(
+ elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" )
+ );
+ }
+}
+
+function cloneCopyEvent( src, dest ) {
+ var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;
+
+ if ( dest.nodeType !== 1 ) {
+ return;
+ }
+
+ // 1. Copy private data: events, handlers, etc.
+ if ( data_priv.hasData( src ) ) {
+ pdataOld = data_priv.access( src );
+ pdataCur = data_priv.set( dest, pdataOld );
+ events = pdataOld.events;
+
+ if ( events ) {
+ delete pdataCur.handle;
+ pdataCur.events = {};
+
+ for ( type in events ) {
+ for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+ jQuery.event.add( dest, type, events[ type ][ i ] );
+ }
+ }
+ }
+ }
+
+ // 2. Copy user data
+ if ( data_user.hasData( src ) ) {
+ udataOld = data_user.access( src );
+ udataCur = jQuery.extend( {}, udataOld );
+
+ data_user.set( dest, udataCur );
+ }
+}
+
+function getAll( context, tag ) {
+ var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) :
+ context.querySelectorAll ? context.querySelectorAll( tag || "*" ) :
+ [];
+
+ return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
+ jQuery.merge( [ context ], ret ) :
+ ret;
+}
+
+// Fix IE bugs, see support tests
+function fixInput( src, dest ) {
+ var nodeName = dest.nodeName.toLowerCase();
+
+ // Fails to persist the checked state of a cloned checkbox or radio button.
+ if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
+ dest.checked = src.checked;
+
+ // Fails to return the selected option to the default selected state when cloning options
+ } else if ( nodeName === "input" || nodeName === "textarea" ) {
+ dest.defaultValue = src.defaultValue;
+ }
+}
+
+jQuery.extend({
+ clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+ var i, l, srcElements, destElements,
+ clone = elem.cloneNode( true ),
+ inPage = jQuery.contains( elem.ownerDocument, elem );
+
+ // Fix IE cloning issues
+ if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
+ !jQuery.isXMLDoc( elem ) ) {
+
+ // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
+ destElements = getAll( clone );
+ srcElements = getAll( elem );
+
+ for ( i = 0, l = srcElements.length; i < l; i++ ) {
+ fixInput( srcElements[ i ], destElements[ i ] );
+ }
+ }
+
+ // Copy the events from the original to the clone
+ if ( dataAndEvents ) {
+ if ( deepDataAndEvents ) {
+ srcElements = srcElements || getAll( elem );
+ destElements = destElements || getAll( clone );
+
+ for ( i = 0, l = srcElements.length; i < l; i++ ) {
+ cloneCopyEvent( srcElements[ i ], destElements[ i ] );
+ }
+ } else {
+ cloneCopyEvent( elem, clone );
+ }
+ }
+
+ // Preserve script evaluation history
+ destElements = getAll( clone, "script" );
+ if ( destElements.length > 0 ) {
+ setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
+ }
+
+ // Return the cloned set
+ return clone;
+ },
+
+ buildFragment: function( elems, context, scripts, selection ) {
+ var elem, tmp, tag, wrap, contains, j,
+ fragment = context.createDocumentFragment(),
+ nodes = [],
+ i = 0,
+ l = elems.length;
+
+ for ( ; i < l; i++ ) {
+ elem = elems[ i ];
+
+ if ( elem || elem === 0 ) {
+
+ // Add nodes directly
+ if ( jQuery.type( elem ) === "object" ) {
+ // Support: QtWebKit, PhantomJS
+ // push.apply(_, arraylike) throws on ancient WebKit
+ jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
+
+ // Convert non-html into a text node
+ } else if ( !rhtml.test( elem ) ) {
+ nodes.push( context.createTextNode( elem ) );
+
+ // Convert html into DOM nodes
+ } else {
+ tmp = tmp || fragment.appendChild( context.createElement("div") );
+
+ // Deserialize a standard representation
+ tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
+ wrap = wrapMap[ tag ] || wrapMap._default;
+ tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1>$2>" ) + wrap[ 2 ];
+
+ // Descend through wrappers to the right content
+ j = wrap[ 0 ];
+ while ( j-- ) {
+ tmp = tmp.lastChild;
+ }
+
+ // Support: QtWebKit, PhantomJS
+ // push.apply(_, arraylike) throws on ancient WebKit
+ jQuery.merge( nodes, tmp.childNodes );
+
+ // Remember the top-level container
+ tmp = fragment.firstChild;
+
+ // Ensure the created nodes are orphaned (#12392)
+ tmp.textContent = "";
+ }
+ }
+ }
+
+ // Remove wrapper from fragment
+ fragment.textContent = "";
+
+ i = 0;
+ while ( (elem = nodes[ i++ ]) ) {
+
+ // #4087 - If origin and destination elements are the same, and this is
+ // that element, do not do anything
+ if ( selection && jQuery.inArray( elem, selection ) !== -1 ) {
+ continue;
+ }
+
+ contains = jQuery.contains( elem.ownerDocument, elem );
+
+ // Append to fragment
+ tmp = getAll( fragment.appendChild( elem ), "script" );
+
+ // Preserve script evaluation history
+ if ( contains ) {
+ setGlobalEval( tmp );
+ }
+
+ // Capture executables
+ if ( scripts ) {
+ j = 0;
+ while ( (elem = tmp[ j++ ]) ) {
+ if ( rscriptType.test( elem.type || "" ) ) {
+ scripts.push( elem );
+ }
+ }
+ }
+ }
+
+ return fragment;
+ },
+
+ cleanData: function( elems ) {
+ var data, elem, type, key,
+ special = jQuery.event.special,
+ i = 0;
+
+ for ( ; (elem = elems[ i ]) !== undefined; i++ ) {
+ if ( jQuery.acceptData( elem ) ) {
+ key = elem[ data_priv.expando ];
+
+ if ( key && (data = data_priv.cache[ key ]) ) {
+ if ( data.events ) {
+ for ( type in data.events ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
+
+ // This is a shortcut to avoid jQuery.event.remove's overhead
+ } else {
+ jQuery.removeEvent( elem, type, data.handle );
+ }
+ }
+ }
+ if ( data_priv.cache[ key ] ) {
+ // Discard any remaining `private` data
+ delete data_priv.cache[ key ];
+ }
+ }
+ }
+ // Discard any remaining `user` data
+ delete data_user.cache[ elem[ data_user.expando ] ];
+ }
+ }
+});
+
+jQuery.fn.extend({
+ text: function( value ) {
+ return access( this, function( value ) {
+ return value === undefined ?
+ jQuery.text( this ) :
+ this.empty().each(function() {
+ if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+ this.textContent = value;
+ }
+ });
+ }, null, value, arguments.length );
+ },
+
+ append: function() {
+ return this.domManip( arguments, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+ var target = manipulationTarget( this, elem );
+ target.appendChild( elem );
+ }
+ });
+ },
+
+ prepend: function() {
+ return this.domManip( arguments, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+ var target = manipulationTarget( this, elem );
+ target.insertBefore( elem, target.firstChild );
+ }
+ });
+ },
+
+ before: function() {
+ return this.domManip( arguments, function( elem ) {
+ if ( this.parentNode ) {
+ this.parentNode.insertBefore( elem, this );
+ }
+ });
+ },
+
+ after: function() {
+ return this.domManip( arguments, function( elem ) {
+ if ( this.parentNode ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ }
+ });
+ },
+
+ remove: function( selector, keepData /* Internal Use Only */ ) {
+ var elem,
+ elems = selector ? jQuery.filter( selector, this ) : this,
+ i = 0;
+
+ for ( ; (elem = elems[i]) != null; i++ ) {
+ if ( !keepData && elem.nodeType === 1 ) {
+ jQuery.cleanData( getAll( elem ) );
+ }
+
+ if ( elem.parentNode ) {
+ if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
+ setGlobalEval( getAll( elem, "script" ) );
+ }
+ elem.parentNode.removeChild( elem );
+ }
+ }
+
+ return this;
+ },
+
+ empty: function() {
+ var elem,
+ i = 0;
+
+ for ( ; (elem = this[i]) != null; i++ ) {
+ if ( elem.nodeType === 1 ) {
+
+ // Prevent memory leaks
+ jQuery.cleanData( getAll( elem, false ) );
+
+ // Remove any remaining nodes
+ elem.textContent = "";
+ }
+ }
+
+ return this;
+ },
+
+ clone: function( dataAndEvents, deepDataAndEvents ) {
+ dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+ deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+ return this.map(function() {
+ return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+ });
+ },
+
+ html: function( value ) {
+ return access( this, function( value ) {
+ var elem = this[ 0 ] || {},
+ i = 0,
+ l = this.length;
+
+ if ( value === undefined && elem.nodeType === 1 ) {
+ return elem.innerHTML;
+ }
+
+ // See if we can take a shortcut and just use innerHTML
+ if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+ !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
+
+ value = value.replace( rxhtmlTag, "<$1>$2>" );
+
+ try {
+ for ( ; i < l; i++ ) {
+ elem = this[ i ] || {};
+
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( getAll( elem, false ) );
+ elem.innerHTML = value;
+ }
+ }
+
+ elem = 0;
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch( e ) {}
+ }
+
+ if ( elem ) {
+ this.empty().append( value );
+ }
+ }, null, value, arguments.length );
+ },
+
+ replaceWith: function() {
+ var arg = arguments[ 0 ];
+
+ // Make the changes, replacing each context element with the new content
+ this.domManip( arguments, function( elem ) {
+ arg = this.parentNode;
+
+ jQuery.cleanData( getAll( this ) );
+
+ if ( arg ) {
+ arg.replaceChild( elem, this );
+ }
+ });
+
+ // Force removal if there was no new content (e.g., from empty arguments)
+ return arg && (arg.length || arg.nodeType) ? this : this.remove();
+ },
+
+ detach: function( selector ) {
+ return this.remove( selector, true );
+ },
+
+ domManip: function( args, callback ) {
+
+ // Flatten any nested arrays
+ args = concat.apply( [], args );
+
+ var fragment, first, scripts, hasScripts, node, doc,
+ i = 0,
+ l = this.length,
+ set = this,
+ iNoClone = l - 1,
+ value = args[ 0 ],
+ isFunction = jQuery.isFunction( value );
+
+ // We can't cloneNode fragments that contain checked, in WebKit
+ if ( isFunction ||
+ ( l > 1 && typeof value === "string" &&
+ !support.checkClone && rchecked.test( value ) ) ) {
+ return this.each(function( index ) {
+ var self = set.eq( index );
+ if ( isFunction ) {
+ args[ 0 ] = value.call( this, index, self.html() );
+ }
+ self.domManip( args, callback );
+ });
+ }
+
+ if ( l ) {
+ fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
+ first = fragment.firstChild;
+
+ if ( fragment.childNodes.length === 1 ) {
+ fragment = first;
+ }
+
+ if ( first ) {
+ scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
+ hasScripts = scripts.length;
+
+ // Use the original fragment for the last item instead of the first because it can end up
+ // being emptied incorrectly in certain situations (#8070).
+ for ( ; i < l; i++ ) {
+ node = fragment;
+
+ if ( i !== iNoClone ) {
+ node = jQuery.clone( node, true, true );
+
+ // Keep references to cloned scripts for later restoration
+ if ( hasScripts ) {
+ // Support: QtWebKit
+ // jQuery.merge because push.apply(_, arraylike) throws
+ jQuery.merge( scripts, getAll( node, "script" ) );
+ }
+ }
+
+ callback.call( this[ i ], node, i );
+ }
+
+ if ( hasScripts ) {
+ doc = scripts[ scripts.length - 1 ].ownerDocument;
+
+ // Reenable scripts
+ jQuery.map( scripts, restoreScript );
+
+ // Evaluate executable scripts on first document insertion
+ for ( i = 0; i < hasScripts; i++ ) {
+ node = scripts[ i ];
+ if ( rscriptType.test( node.type || "" ) &&
+ !data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) {
+
+ if ( node.src ) {
+ // Optional AJAX dependency, but won't run scripts if not present
+ if ( jQuery._evalUrl ) {
+ jQuery._evalUrl( node.src );
+ }
+ } else {
+ jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return this;
+ }
+});
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var elems,
+ ret = [],
+ insert = jQuery( selector ),
+ last = insert.length - 1,
+ i = 0;
+
+ for ( ; i <= last; i++ ) {
+ elems = i === last ? this : this.clone( true );
+ jQuery( insert[ i ] )[ original ]( elems );
+
+ // Support: QtWebKit
+ // .get() because push.apply(_, arraylike) throws
+ push.apply( ret, elems.get() );
+ }
+
+ return this.pushStack( ret );
+ };
+});
+
+
+var iframe,
+ elemdisplay = {};
+
+/**
+ * Retrieve the actual display of a element
+ * @param {String} name nodeName of the element
+ * @param {Object} doc Document object
+ */
+// Called only from within defaultDisplay
+function actualDisplay( name, doc ) {
+ var style,
+ elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),
+
+ // getDefaultComputedStyle might be reliably used only on attached element
+ display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ?
+
+ // Use of this method is a temporary fix (more like optimization) until something better comes along,
+ // since it was removed from specification and supported only in FF
+ style.display : jQuery.css( elem[ 0 ], "display" );
+
+ // We don't have any data stored on the element,
+ // so use "detach" method as fast way to get rid of the element
+ elem.detach();
+
+ return display;
+}
+
+/**
+ * Try to determine the default display value of an element
+ * @param {String} nodeName
+ */
+function defaultDisplay( nodeName ) {
+ var doc = document,
+ display = elemdisplay[ nodeName ];
+
+ if ( !display ) {
+ display = actualDisplay( nodeName, doc );
+
+ // If the simple way fails, read from inside an iframe
+ if ( display === "none" || !display ) {
+
+ // Use the already-created iframe if possible
+ iframe = (iframe || jQuery( "" )).appendTo( doc.documentElement );
+
+ // Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
+ doc = iframe[ 0 ].contentDocument;
+
+ // Support: IE
+ doc.write();
+ doc.close();
+
+ display = actualDisplay( nodeName, doc );
+ iframe.detach();
+ }
+
+ // Store the correct default display
+ elemdisplay[ nodeName ] = display;
+ }
+
+ return display;
+}
+var rmargin = (/^margin/);
+
+var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
+
+var getStyles = function( elem ) {
+ // Support: IE<=11+, Firefox<=30+ (#15098, #14150)
+ // IE throws on elements created in popups
+ // FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
+ if ( elem.ownerDocument.defaultView.opener ) {
+ return elem.ownerDocument.defaultView.getComputedStyle( elem, null );
+ }
+
+ return window.getComputedStyle( elem, null );
+ };
+
+
+
+function curCSS( elem, name, computed ) {
+ var width, minWidth, maxWidth, ret,
+ style = elem.style;
+
+ computed = computed || getStyles( elem );
+
+ // Support: IE9
+ // getPropertyValue is only needed for .css('filter') (#12537)
+ if ( computed ) {
+ ret = computed.getPropertyValue( name ) || computed[ name ];
+ }
+
+ if ( computed ) {
+
+ if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
+ ret = jQuery.style( elem, name );
+ }
+
+ // Support: iOS < 6
+ // A tribute to the "awesome hack by Dean Edwards"
+ // iOS < 6 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
+ // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+ if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
+
+ // Remember the original values
+ width = style.width;
+ minWidth = style.minWidth;
+ maxWidth = style.maxWidth;
+
+ // Put in the new values to get a computed value out
+ style.minWidth = style.maxWidth = style.width = ret;
+ ret = computed.width;
+
+ // Revert the changed values
+ style.width = width;
+ style.minWidth = minWidth;
+ style.maxWidth = maxWidth;
+ }
+ }
+
+ return ret !== undefined ?
+ // Support: IE
+ // IE returns zIndex value as an integer.
+ ret + "" :
+ ret;
+}
+
+
+function addGetHookIf( conditionFn, hookFn ) {
+ // Define the hook, we'll check on the first run if it's really needed.
+ return {
+ get: function() {
+ if ( conditionFn() ) {
+ // Hook not needed (or it's not possible to use it due
+ // to missing dependency), remove it.
+ delete this.get;
+ return;
+ }
+
+ // Hook needed; redefine it so that the support test is not executed again.
+ return (this.get = hookFn).apply( this, arguments );
+ }
+ };
+}
+
+
+(function() {
+ var pixelPositionVal, boxSizingReliableVal,
+ docElem = document.documentElement,
+ container = document.createElement( "div" ),
+ div = document.createElement( "div" );
+
+ if ( !div.style ) {
+ return;
+ }
+
+ // Support: IE9-11+
+ // Style of cloned element affects source element cloned (#8908)
+ div.style.backgroundClip = "content-box";
+ div.cloneNode( true ).style.backgroundClip = "";
+ support.clearCloneStyle = div.style.backgroundClip === "content-box";
+
+ container.style.cssText = "border:0;width:0;height:0;top:0;left:-9999px;margin-top:1px;" +
+ "position:absolute";
+ container.appendChild( div );
+
+ // Executing both pixelPosition & boxSizingReliable tests require only one layout
+ // so they're executed at the same time to save the second computation.
+ function computePixelPositionAndBoxSizingReliable() {
+ div.style.cssText =
+ // Support: Firefox<29, Android 2.3
+ // Vendor-prefix box-sizing
+ "-webkit-box-sizing:border-box;-moz-box-sizing:border-box;" +
+ "box-sizing:border-box;display:block;margin-top:1%;top:1%;" +
+ "border:1px;padding:1px;width:4px;position:absolute";
+ div.innerHTML = "";
+ docElem.appendChild( container );
+
+ var divStyle = window.getComputedStyle( div, null );
+ pixelPositionVal = divStyle.top !== "1%";
+ boxSizingReliableVal = divStyle.width === "4px";
+
+ docElem.removeChild( container );
+ }
+
+ // Support: node.js jsdom
+ // Don't assume that getComputedStyle is a property of the global object
+ if ( window.getComputedStyle ) {
+ jQuery.extend( support, {
+ pixelPosition: function() {
+
+ // This test is executed only once but we still do memoizing
+ // since we can use the boxSizingReliable pre-computing.
+ // No need to check if the test was already performed, though.
+ computePixelPositionAndBoxSizingReliable();
+ return pixelPositionVal;
+ },
+ boxSizingReliable: function() {
+ if ( boxSizingReliableVal == null ) {
+ computePixelPositionAndBoxSizingReliable();
+ }
+ return boxSizingReliableVal;
+ },
+ reliableMarginRight: function() {
+
+ // Support: Android 2.3
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. (#3333)
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ // This support function is only executed once so no memoizing is needed.
+ var ret,
+ marginDiv = div.appendChild( document.createElement( "div" ) );
+
+ // Reset CSS: box-sizing; display; margin; border; padding
+ marginDiv.style.cssText = div.style.cssText =
+ // Support: Firefox<29, Android 2.3
+ // Vendor-prefix box-sizing
+ "-webkit-box-sizing:content-box;-moz-box-sizing:content-box;" +
+ "box-sizing:content-box;display:block;margin:0;border:0;padding:0";
+ marginDiv.style.marginRight = marginDiv.style.width = "0";
+ div.style.width = "1px";
+ docElem.appendChild( container );
+
+ ret = !parseFloat( window.getComputedStyle( marginDiv, null ).marginRight );
+
+ docElem.removeChild( container );
+ div.removeChild( marginDiv );
+
+ return ret;
+ }
+ });
+ }
+})();
+
+
+// A method for quickly swapping in/out CSS properties to get correct calculations.
+jQuery.swap = function( elem, options, callback, args ) {
+ var ret, name,
+ old = {};
+
+ // Remember the old values, and insert the new ones
+ for ( name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ ret = callback.apply( elem, args || [] );
+
+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+
+ return ret;
+};
+
+
+var
+ // Swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
+ // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
+ rdisplayswap = /^(none|table(?!-c[ea]).+)/,
+ rnumsplit = new RegExp( "^(" + pnum + ")(.*)$", "i" ),
+ rrelNum = new RegExp( "^([+-])=(" + pnum + ")", "i" ),
+
+ cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+ cssNormalTransform = {
+ letterSpacing: "0",
+ fontWeight: "400"
+ },
+
+ cssPrefixes = [ "Webkit", "O", "Moz", "ms" ];
+
+// Return a css property mapped to a potentially vendor prefixed property
+function vendorPropName( style, name ) {
+
+ // Shortcut for names that are not vendor prefixed
+ if ( name in style ) {
+ return name;
+ }
+
+ // Check for vendor prefixed names
+ var capName = name[0].toUpperCase() + name.slice(1),
+ origName = name,
+ i = cssPrefixes.length;
+
+ while ( i-- ) {
+ name = cssPrefixes[ i ] + capName;
+ if ( name in style ) {
+ return name;
+ }
+ }
+
+ return origName;
+}
+
+function setPositiveNumber( elem, value, subtract ) {
+ var matches = rnumsplit.exec( value );
+ return matches ?
+ // Guard against undefined "subtract", e.g., when used as in cssHooks
+ Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
+ value;
+}
+
+function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
+ var i = extra === ( isBorderBox ? "border" : "content" ) ?
+ // If we already have the right measurement, avoid augmentation
+ 4 :
+ // Otherwise initialize for horizontal or vertical properties
+ name === "width" ? 1 : 0,
+
+ val = 0;
+
+ for ( ; i < 4; i += 2 ) {
+ // Both box models exclude margin, so add it if we want it
+ if ( extra === "margin" ) {
+ val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
+ }
+
+ if ( isBorderBox ) {
+ // border-box includes padding, so remove it if we want content
+ if ( extra === "content" ) {
+ val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+ }
+
+ // At this point, extra isn't border nor margin, so remove border
+ if ( extra !== "margin" ) {
+ val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+ }
+ } else {
+ // At this point, extra isn't content, so add padding
+ val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+
+ // At this point, extra isn't content nor padding, so add border
+ if ( extra !== "padding" ) {
+ val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+ }
+ }
+ }
+
+ return val;
+}
+
+function getWidthOrHeight( elem, name, extra ) {
+
+ // Start with offset property, which is equivalent to the border-box value
+ var valueIsBorderBox = true,
+ val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+ styles = getStyles( elem ),
+ isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
+
+ // Some non-html elements return undefined for offsetWidth, so check for null/undefined
+ // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
+ // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
+ if ( val <= 0 || val == null ) {
+ // Fall back to computed then uncomputed css if necessary
+ val = curCSS( elem, name, styles );
+ if ( val < 0 || val == null ) {
+ val = elem.style[ name ];
+ }
+
+ // Computed unit is not pixels. Stop here and return.
+ if ( rnumnonpx.test(val) ) {
+ return val;
+ }
+
+ // Check for style in case a browser which returns unreliable values
+ // for getComputedStyle silently falls back to the reliable elem.style
+ valueIsBorderBox = isBorderBox &&
+ ( support.boxSizingReliable() || val === elem.style[ name ] );
+
+ // Normalize "", auto, and prepare for extra
+ val = parseFloat( val ) || 0;
+ }
+
+ // Use the active box-sizing model to add/subtract irrelevant styles
+ return ( val +
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra || ( isBorderBox ? "border" : "content" ),
+ valueIsBorderBox,
+ styles
+ )
+ ) + "px";
+}
+
+function showHide( elements, show ) {
+ var display, elem, hidden,
+ values = [],
+ index = 0,
+ length = elements.length;
+
+ for ( ; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+
+ values[ index ] = data_priv.get( elem, "olddisplay" );
+ display = elem.style.display;
+ if ( show ) {
+ // Reset the inline display of this element to learn if it is
+ // being hidden by cascaded rules or not
+ if ( !values[ index ] && display === "none" ) {
+ elem.style.display = "";
+ }
+
+ // Set elements which have been overridden with display: none
+ // in a stylesheet to whatever the default browser style is
+ // for such an element
+ if ( elem.style.display === "" && isHidden( elem ) ) {
+ values[ index ] = data_priv.access( elem, "olddisplay", defaultDisplay(elem.nodeName) );
+ }
+ } else {
+ hidden = isHidden( elem );
+
+ if ( display !== "none" || !hidden ) {
+ data_priv.set( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) );
+ }
+ }
+ }
+
+ // Set the display of most of the elements in a second loop
+ // to avoid the constant reflow
+ for ( index = 0; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+ if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
+ elem.style.display = show ? values[ index ] || "" : "none";
+ }
+ }
+
+ return elements;
+}
+
+jQuery.extend({
+
+ // Add in style property hooks for overriding the default
+ // behavior of getting and setting a style property
+ cssHooks: {
+ opacity: {
+ get: function( elem, computed ) {
+ if ( computed ) {
+
+ // We should always get a number back from opacity
+ var ret = curCSS( elem, "opacity" );
+ return ret === "" ? "1" : ret;
+ }
+ }
+ }
+ },
+
+ // Don't automatically add "px" to these possibly-unitless properties
+ cssNumber: {
+ "columnCount": true,
+ "fillOpacity": true,
+ "flexGrow": true,
+ "flexShrink": true,
+ "fontWeight": true,
+ "lineHeight": true,
+ "opacity": true,
+ "order": true,
+ "orphans": true,
+ "widows": true,
+ "zIndex": true,
+ "zoom": true
+ },
+
+ // Add in properties whose names you wish to fix before
+ // setting or getting the value
+ cssProps: {
+ "float": "cssFloat"
+ },
+
+ // Get and set the style property on a DOM Node
+ style: function( elem, name, value, extra ) {
+
+ // Don't set styles on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+ return;
+ }
+
+ // Make sure that we're working with the right name
+ var ret, type, hooks,
+ origName = jQuery.camelCase( name ),
+ style = elem.style;
+
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
+
+ // Gets hook for the prefixed version, then unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // Check if we're setting a value
+ if ( value !== undefined ) {
+ type = typeof value;
+
+ // Convert "+=" or "-=" to relative numbers (#7345)
+ if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+ value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
+ // Fixes bug #9237
+ type = "number";
+ }
+
+ // Make sure that null and NaN values aren't set (#7116)
+ if ( value == null || value !== value ) {
+ return;
+ }
+
+ // If a number, add 'px' to the (except for certain CSS properties)
+ if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+ value += "px";
+ }
+
+ // Support: IE9-11+
+ // background-* props affect original clone's values
+ if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
+ style[ name ] = "inherit";
+ }
+
+ // If a hook was provided, use that value, otherwise just set the specified value
+ if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
+ style[ name ] = value;
+ }
+
+ } else {
+ // If a hook was provided get the non-computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+ return ret;
+ }
+
+ // Otherwise just get the value from the style object
+ return style[ name ];
+ }
+ },
+
+ css: function( elem, name, extra, styles ) {
+ var val, num, hooks,
+ origName = jQuery.camelCase( name );
+
+ // Make sure that we're working with the right name
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
+
+ // Try prefixed name followed by the unprefixed name
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // If a hook was provided get the computed value from there
+ if ( hooks && "get" in hooks ) {
+ val = hooks.get( elem, true, extra );
+ }
+
+ // Otherwise, if a way to get the computed value exists, use that
+ if ( val === undefined ) {
+ val = curCSS( elem, name, styles );
+ }
+
+ // Convert "normal" to computed value
+ if ( val === "normal" && name in cssNormalTransform ) {
+ val = cssNormalTransform[ name ];
+ }
+
+ // Make numeric if forced or a qualifier was provided and val looks numeric
+ if ( extra === "" || extra ) {
+ num = parseFloat( val );
+ return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
+ }
+ return val;
+ }
+});
+
+jQuery.each([ "height", "width" ], function( i, name ) {
+ jQuery.cssHooks[ name ] = {
+ get: function( elem, computed, extra ) {
+ if ( computed ) {
+
+ // Certain elements can have dimension info if we invisibly show them
+ // but it must have a current display style that would benefit
+ return rdisplayswap.test( jQuery.css( elem, "display" ) ) && elem.offsetWidth === 0 ?
+ jQuery.swap( elem, cssShow, function() {
+ return getWidthOrHeight( elem, name, extra );
+ }) :
+ getWidthOrHeight( elem, name, extra );
+ }
+ },
+
+ set: function( elem, value, extra ) {
+ var styles = extra && getStyles( elem );
+ return setPositiveNumber( elem, value, extra ?
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra,
+ jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
+ styles
+ ) : 0
+ );
+ }
+ };
+});
+
+// Support: Android 2.3
+jQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight,
+ function( elem, computed ) {
+ if ( computed ) {
+ return jQuery.swap( elem, { "display": "inline-block" },
+ curCSS, [ elem, "marginRight" ] );
+ }
+ }
+);
+
+// These hooks are used by animate to expand properties
+jQuery.each({
+ margin: "",
+ padding: "",
+ border: "Width"
+}, function( prefix, suffix ) {
+ jQuery.cssHooks[ prefix + suffix ] = {
+ expand: function( value ) {
+ var i = 0,
+ expanded = {},
+
+ // Assumes a single number if not a string
+ parts = typeof value === "string" ? value.split(" ") : [ value ];
+
+ for ( ; i < 4; i++ ) {
+ expanded[ prefix + cssExpand[ i ] + suffix ] =
+ parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+ }
+
+ return expanded;
+ }
+ };
+
+ if ( !rmargin.test( prefix ) ) {
+ jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+ }
+});
+
+jQuery.fn.extend({
+ css: function( name, value ) {
+ return access( this, function( elem, name, value ) {
+ var styles, len,
+ map = {},
+ i = 0;
+
+ if ( jQuery.isArray( name ) ) {
+ styles = getStyles( elem );
+ len = name.length;
+
+ for ( ; i < len; i++ ) {
+ map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
+ }
+
+ return map;
+ }
+
+ return value !== undefined ?
+ jQuery.style( elem, name, value ) :
+ jQuery.css( elem, name );
+ }, name, value, arguments.length > 1 );
+ },
+ show: function() {
+ return showHide( this, true );
+ },
+ hide: function() {
+ return showHide( this );
+ },
+ toggle: function( state ) {
+ if ( typeof state === "boolean" ) {
+ return state ? this.show() : this.hide();
+ }
+
+ return this.each(function() {
+ if ( isHidden( this ) ) {
+ jQuery( this ).show();
+ } else {
+ jQuery( this ).hide();
+ }
+ });
+ }
+});
+
+
+function Tween( elem, options, prop, end, easing ) {
+ return new Tween.prototype.init( elem, options, prop, end, easing );
+}
+jQuery.Tween = Tween;
+
+Tween.prototype = {
+ constructor: Tween,
+ init: function( elem, options, prop, end, easing, unit ) {
+ this.elem = elem;
+ this.prop = prop;
+ this.easing = easing || "swing";
+ this.options = options;
+ this.start = this.now = this.cur();
+ this.end = end;
+ this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+ },
+ cur: function() {
+ var hooks = Tween.propHooks[ this.prop ];
+
+ return hooks && hooks.get ?
+ hooks.get( this ) :
+ Tween.propHooks._default.get( this );
+ },
+ run: function( percent ) {
+ var eased,
+ hooks = Tween.propHooks[ this.prop ];
+
+ if ( this.options.duration ) {
+ this.pos = eased = jQuery.easing[ this.easing ](
+ percent, this.options.duration * percent, 0, 1, this.options.duration
+ );
+ } else {
+ this.pos = eased = percent;
+ }
+ this.now = ( this.end - this.start ) * eased + this.start;
+
+ if ( this.options.step ) {
+ this.options.step.call( this.elem, this.now, this );
+ }
+
+ if ( hooks && hooks.set ) {
+ hooks.set( this );
+ } else {
+ Tween.propHooks._default.set( this );
+ }
+ return this;
+ }
+};
+
+Tween.prototype.init.prototype = Tween.prototype;
+
+Tween.propHooks = {
+ _default: {
+ get: function( tween ) {
+ var result;
+
+ if ( tween.elem[ tween.prop ] != null &&
+ (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
+ return tween.elem[ tween.prop ];
+ }
+
+ // Passing an empty string as a 3rd parameter to .css will automatically
+ // attempt a parseFloat and fallback to a string if the parse fails.
+ // Simple values such as "10px" are parsed to Float;
+ // complex values such as "rotate(1rad)" are returned as-is.
+ result = jQuery.css( tween.elem, tween.prop, "" );
+ // Empty strings, null, undefined and "auto" are converted to 0.
+ return !result || result === "auto" ? 0 : result;
+ },
+ set: function( tween ) {
+ // Use step hook for back compat.
+ // Use cssHook if its there.
+ // Use .style if available and use plain properties where available.
+ if ( jQuery.fx.step[ tween.prop ] ) {
+ jQuery.fx.step[ tween.prop ]( tween );
+ } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
+ jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+ } else {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+ }
+};
+
+// Support: IE9
+// Panic based approach to setting things on disconnected nodes
+Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+ set: function( tween ) {
+ if ( tween.elem.nodeType && tween.elem.parentNode ) {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+};
+
+jQuery.easing = {
+ linear: function( p ) {
+ return p;
+ },
+ swing: function( p ) {
+ return 0.5 - Math.cos( p * Math.PI ) / 2;
+ }
+};
+
+jQuery.fx = Tween.prototype.init;
+
+// Back Compat <1.8 extension point
+jQuery.fx.step = {};
+
+
+
+
+var
+ fxNow, timerId,
+ rfxtypes = /^(?:toggle|show|hide)$/,
+ rfxnum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ),
+ rrun = /queueHooks$/,
+ animationPrefilters = [ defaultPrefilter ],
+ tweeners = {
+ "*": [ function( prop, value ) {
+ var tween = this.createTween( prop, value ),
+ target = tween.cur(),
+ parts = rfxnum.exec( value ),
+ unit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
+
+ // Starting value computation is required for potential unit mismatches
+ start = ( jQuery.cssNumber[ prop ] || unit !== "px" && +target ) &&
+ rfxnum.exec( jQuery.css( tween.elem, prop ) ),
+ scale = 1,
+ maxIterations = 20;
+
+ if ( start && start[ 3 ] !== unit ) {
+ // Trust units reported by jQuery.css
+ unit = unit || start[ 3 ];
+
+ // Make sure we update the tween properties later on
+ parts = parts || [];
+
+ // Iteratively approximate from a nonzero starting point
+ start = +target || 1;
+
+ do {
+ // If previous iteration zeroed out, double until we get *something*.
+ // Use string for doubling so we don't accidentally see scale as unchanged below
+ scale = scale || ".5";
+
+ // Adjust and apply
+ start = start / scale;
+ jQuery.style( tween.elem, prop, start + unit );
+
+ // Update scale, tolerating zero or NaN from tween.cur(),
+ // break the loop if scale is unchanged or perfect, or if we've just had enough
+ } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
+ }
+
+ // Update tween properties
+ if ( parts ) {
+ start = tween.start = +start || +target || 0;
+ tween.unit = unit;
+ // If a +=/-= token was provided, we're doing a relative animation
+ tween.end = parts[ 1 ] ?
+ start + ( parts[ 1 ] + 1 ) * parts[ 2 ] :
+ +parts[ 2 ];
+ }
+
+ return tween;
+ } ]
+ };
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+ setTimeout(function() {
+ fxNow = undefined;
+ });
+ return ( fxNow = jQuery.now() );
+}
+
+// Generate parameters to create a standard animation
+function genFx( type, includeWidth ) {
+ var which,
+ i = 0,
+ attrs = { height: type };
+
+ // If we include width, step value is 1 to do all cssExpand values,
+ // otherwise step value is 2 to skip over Left and Right
+ includeWidth = includeWidth ? 1 : 0;
+ for ( ; i < 4 ; i += 2 - includeWidth ) {
+ which = cssExpand[ i ];
+ attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+ }
+
+ if ( includeWidth ) {
+ attrs.opacity = attrs.width = type;
+ }
+
+ return attrs;
+}
+
+function createTween( value, prop, animation ) {
+ var tween,
+ collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
+ index = 0,
+ length = collection.length;
+ for ( ; index < length; index++ ) {
+ if ( (tween = collection[ index ].call( animation, prop, value )) ) {
+
+ // We're done with this property
+ return tween;
+ }
+ }
+}
+
+function defaultPrefilter( elem, props, opts ) {
+ /* jshint validthis: true */
+ var prop, value, toggle, tween, hooks, oldfire, display, checkDisplay,
+ anim = this,
+ orig = {},
+ style = elem.style,
+ hidden = elem.nodeType && isHidden( elem ),
+ dataShow = data_priv.get( elem, "fxshow" );
+
+ // Handle queue: false promises
+ if ( !opts.queue ) {
+ hooks = jQuery._queueHooks( elem, "fx" );
+ if ( hooks.unqueued == null ) {
+ hooks.unqueued = 0;
+ oldfire = hooks.empty.fire;
+ hooks.empty.fire = function() {
+ if ( !hooks.unqueued ) {
+ oldfire();
+ }
+ };
+ }
+ hooks.unqueued++;
+
+ anim.always(function() {
+ // Ensure the complete handler is called before this completes
+ anim.always(function() {
+ hooks.unqueued--;
+ if ( !jQuery.queue( elem, "fx" ).length ) {
+ hooks.empty.fire();
+ }
+ });
+ });
+ }
+
+ // Height/width overflow pass
+ if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
+ // Make sure that nothing sneaks out
+ // Record all 3 overflow attributes because IE9-10 do not
+ // change the overflow attribute when overflowX and
+ // overflowY are set to the same value
+ opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
+
+ // Set display property to inline-block for height/width
+ // animations on inline elements that are having width/height animated
+ display = jQuery.css( elem, "display" );
+
+ // Test default display if display is currently "none"
+ checkDisplay = display === "none" ?
+ data_priv.get( elem, "olddisplay" ) || defaultDisplay( elem.nodeName ) : display;
+
+ if ( checkDisplay === "inline" && jQuery.css( elem, "float" ) === "none" ) {
+ style.display = "inline-block";
+ }
+ }
+
+ if ( opts.overflow ) {
+ style.overflow = "hidden";
+ anim.always(function() {
+ style.overflow = opts.overflow[ 0 ];
+ style.overflowX = opts.overflow[ 1 ];
+ style.overflowY = opts.overflow[ 2 ];
+ });
+ }
+
+ // show/hide pass
+ for ( prop in props ) {
+ value = props[ prop ];
+ if ( rfxtypes.exec( value ) ) {
+ delete props[ prop ];
+ toggle = toggle || value === "toggle";
+ if ( value === ( hidden ? "hide" : "show" ) ) {
+
+ // If there is dataShow left over from a stopped hide or show and we are going to proceed with show, we should pretend to be hidden
+ if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
+ hidden = true;
+ } else {
+ continue;
+ }
+ }
+ orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
+
+ // Any non-fx value stops us from restoring the original display value
+ } else {
+ display = undefined;
+ }
+ }
+
+ if ( !jQuery.isEmptyObject( orig ) ) {
+ if ( dataShow ) {
+ if ( "hidden" in dataShow ) {
+ hidden = dataShow.hidden;
+ }
+ } else {
+ dataShow = data_priv.access( elem, "fxshow", {} );
+ }
+
+ // Store state if its toggle - enables .stop().toggle() to "reverse"
+ if ( toggle ) {
+ dataShow.hidden = !hidden;
+ }
+ if ( hidden ) {
+ jQuery( elem ).show();
+ } else {
+ anim.done(function() {
+ jQuery( elem ).hide();
+ });
+ }
+ anim.done(function() {
+ var prop;
+
+ data_priv.remove( elem, "fxshow" );
+ for ( prop in orig ) {
+ jQuery.style( elem, prop, orig[ prop ] );
+ }
+ });
+ for ( prop in orig ) {
+ tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
+
+ if ( !( prop in dataShow ) ) {
+ dataShow[ prop ] = tween.start;
+ if ( hidden ) {
+ tween.end = tween.start;
+ tween.start = prop === "width" || prop === "height" ? 1 : 0;
+ }
+ }
+ }
+
+ // If this is a noop like .hide().hide(), restore an overwritten display value
+ } else if ( (display === "none" ? defaultDisplay( elem.nodeName ) : display) === "inline" ) {
+ style.display = display;
+ }
+}
+
+function propFilter( props, specialEasing ) {
+ var index, name, easing, value, hooks;
+
+ // camelCase, specialEasing and expand cssHook pass
+ for ( index in props ) {
+ name = jQuery.camelCase( index );
+ easing = specialEasing[ name ];
+ value = props[ index ];
+ if ( jQuery.isArray( value ) ) {
+ easing = value[ 1 ];
+ value = props[ index ] = value[ 0 ];
+ }
+
+ if ( index !== name ) {
+ props[ name ] = value;
+ delete props[ index ];
+ }
+
+ hooks = jQuery.cssHooks[ name ];
+ if ( hooks && "expand" in hooks ) {
+ value = hooks.expand( value );
+ delete props[ name ];
+
+ // Not quite $.extend, this won't overwrite existing keys.
+ // Reusing 'index' because we have the correct "name"
+ for ( index in value ) {
+ if ( !( index in props ) ) {
+ props[ index ] = value[ index ];
+ specialEasing[ index ] = easing;
+ }
+ }
+ } else {
+ specialEasing[ name ] = easing;
+ }
+ }
+}
+
+function Animation( elem, properties, options ) {
+ var result,
+ stopped,
+ index = 0,
+ length = animationPrefilters.length,
+ deferred = jQuery.Deferred().always( function() {
+ // Don't match elem in the :animated selector
+ delete tick.elem;
+ }),
+ tick = function() {
+ if ( stopped ) {
+ return false;
+ }
+ var currentTime = fxNow || createFxNow(),
+ remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+ // Support: Android 2.3
+ // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
+ temp = remaining / animation.duration || 0,
+ percent = 1 - temp,
+ index = 0,
+ length = animation.tweens.length;
+
+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( percent );
+ }
+
+ deferred.notifyWith( elem, [ animation, percent, remaining ]);
+
+ if ( percent < 1 && length ) {
+ return remaining;
+ } else {
+ deferred.resolveWith( elem, [ animation ] );
+ return false;
+ }
+ },
+ animation = deferred.promise({
+ elem: elem,
+ props: jQuery.extend( {}, properties ),
+ opts: jQuery.extend( true, { specialEasing: {} }, options ),
+ originalProperties: properties,
+ originalOptions: options,
+ startTime: fxNow || createFxNow(),
+ duration: options.duration,
+ tweens: [],
+ createTween: function( prop, end ) {
+ var tween = jQuery.Tween( elem, animation.opts, prop, end,
+ animation.opts.specialEasing[ prop ] || animation.opts.easing );
+ animation.tweens.push( tween );
+ return tween;
+ },
+ stop: function( gotoEnd ) {
+ var index = 0,
+ // If we are going to the end, we want to run all the tweens
+ // otherwise we skip this part
+ length = gotoEnd ? animation.tweens.length : 0;
+ if ( stopped ) {
+ return this;
+ }
+ stopped = true;
+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( 1 );
+ }
+
+ // Resolve when we played the last frame; otherwise, reject
+ if ( gotoEnd ) {
+ deferred.resolveWith( elem, [ animation, gotoEnd ] );
+ } else {
+ deferred.rejectWith( elem, [ animation, gotoEnd ] );
+ }
+ return this;
+ }
+ }),
+ props = animation.props;
+
+ propFilter( props, animation.opts.specialEasing );
+
+ for ( ; index < length ; index++ ) {
+ result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
+ if ( result ) {
+ return result;
+ }
+ }
+
+ jQuery.map( props, createTween, animation );
+
+ if ( jQuery.isFunction( animation.opts.start ) ) {
+ animation.opts.start.call( elem, animation );
+ }
+
+ jQuery.fx.timer(
+ jQuery.extend( tick, {
+ elem: elem,
+ anim: animation,
+ queue: animation.opts.queue
+ })
+ );
+
+ // attach callbacks from options
+ return animation.progress( animation.opts.progress )
+ .done( animation.opts.done, animation.opts.complete )
+ .fail( animation.opts.fail )
+ .always( animation.opts.always );
+}
+
+jQuery.Animation = jQuery.extend( Animation, {
+
+ tweener: function( props, callback ) {
+ if ( jQuery.isFunction( props ) ) {
+ callback = props;
+ props = [ "*" ];
+ } else {
+ props = props.split(" ");
+ }
+
+ var prop,
+ index = 0,
+ length = props.length;
+
+ for ( ; index < length ; index++ ) {
+ prop = props[ index ];
+ tweeners[ prop ] = tweeners[ prop ] || [];
+ tweeners[ prop ].unshift( callback );
+ }
+ },
+
+ prefilter: function( callback, prepend ) {
+ if ( prepend ) {
+ animationPrefilters.unshift( callback );
+ } else {
+ animationPrefilters.push( callback );
+ }
+ }
+});
+
+jQuery.speed = function( speed, easing, fn ) {
+ var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+ };
+
+ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+ opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+ // Normalize opt.queue - true/undefined/null -> "fx"
+ if ( opt.queue == null || opt.queue === true ) {
+ opt.queue = "fx";
+ }
+
+ // Queueing
+ opt.old = opt.complete;
+
+ opt.complete = function() {
+ if ( jQuery.isFunction( opt.old ) ) {
+ opt.old.call( this );
+ }
+
+ if ( opt.queue ) {
+ jQuery.dequeue( this, opt.queue );
+ }
+ };
+
+ return opt;
+};
+
+jQuery.fn.extend({
+ fadeTo: function( speed, to, easing, callback ) {
+
+ // Show any hidden elements after setting opacity to 0
+ return this.filter( isHidden ).css( "opacity", 0 ).show()
+
+ // Animate to the value specified
+ .end().animate({ opacity: to }, speed, easing, callback );
+ },
+ animate: function( prop, speed, easing, callback ) {
+ var empty = jQuery.isEmptyObject( prop ),
+ optall = jQuery.speed( speed, easing, callback ),
+ doAnimation = function() {
+ // Operate on a copy of prop so per-property easing won't be lost
+ var anim = Animation( this, jQuery.extend( {}, prop ), optall );
+
+ // Empty animations, or finishing resolves immediately
+ if ( empty || data_priv.get( this, "finish" ) ) {
+ anim.stop( true );
+ }
+ };
+ doAnimation.finish = doAnimation;
+
+ return empty || optall.queue === false ?
+ this.each( doAnimation ) :
+ this.queue( optall.queue, doAnimation );
+ },
+ stop: function( type, clearQueue, gotoEnd ) {
+ var stopQueue = function( hooks ) {
+ var stop = hooks.stop;
+ delete hooks.stop;
+ stop( gotoEnd );
+ };
+
+ if ( typeof type !== "string" ) {
+ gotoEnd = clearQueue;
+ clearQueue = type;
+ type = undefined;
+ }
+ if ( clearQueue && type !== false ) {
+ this.queue( type || "fx", [] );
+ }
+
+ return this.each(function() {
+ var dequeue = true,
+ index = type != null && type + "queueHooks",
+ timers = jQuery.timers,
+ data = data_priv.get( this );
+
+ if ( index ) {
+ if ( data[ index ] && data[ index ].stop ) {
+ stopQueue( data[ index ] );
+ }
+ } else {
+ for ( index in data ) {
+ if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+ stopQueue( data[ index ] );
+ }
+ }
+ }
+
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+ timers[ index ].anim.stop( gotoEnd );
+ dequeue = false;
+ timers.splice( index, 1 );
+ }
+ }
+
+ // Start the next in the queue if the last step wasn't forced.
+ // Timers currently will call their complete callbacks, which
+ // will dequeue but only if they were gotoEnd.
+ if ( dequeue || !gotoEnd ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ finish: function( type ) {
+ if ( type !== false ) {
+ type = type || "fx";
+ }
+ return this.each(function() {
+ var index,
+ data = data_priv.get( this ),
+ queue = data[ type + "queue" ],
+ hooks = data[ type + "queueHooks" ],
+ timers = jQuery.timers,
+ length = queue ? queue.length : 0;
+
+ // Enable finishing flag on private data
+ data.finish = true;
+
+ // Empty the queue first
+ jQuery.queue( this, type, [] );
+
+ if ( hooks && hooks.stop ) {
+ hooks.stop.call( this, true );
+ }
+
+ // Look for any active animations, and finish them
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
+ timers[ index ].anim.stop( true );
+ timers.splice( index, 1 );
+ }
+ }
+
+ // Look for any animations in the old queue and finish them
+ for ( index = 0; index < length; index++ ) {
+ if ( queue[ index ] && queue[ index ].finish ) {
+ queue[ index ].finish.call( this );
+ }
+ }
+
+ // Turn off finishing flag
+ delete data.finish;
+ });
+ }
+});
+
+jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
+ var cssFn = jQuery.fn[ name ];
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return speed == null || typeof speed === "boolean" ?
+ cssFn.apply( this, arguments ) :
+ this.animate( genFx( name, true ), speed, easing, callback );
+ };
+});
+
+// Generate shortcuts for custom animations
+jQuery.each({
+ slideDown: genFx("show"),
+ slideUp: genFx("hide"),
+ slideToggle: genFx("toggle"),
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" },
+ fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return this.animate( props, speed, easing, callback );
+ };
+});
+
+jQuery.timers = [];
+jQuery.fx.tick = function() {
+ var timer,
+ i = 0,
+ timers = jQuery.timers;
+
+ fxNow = jQuery.now();
+
+ for ( ; i < timers.length; i++ ) {
+ timer = timers[ i ];
+ // Checks the timer has not already been removed
+ if ( !timer() && timers[ i ] === timer ) {
+ timers.splice( i--, 1 );
+ }
+ }
+
+ if ( !timers.length ) {
+ jQuery.fx.stop();
+ }
+ fxNow = undefined;
+};
+
+jQuery.fx.timer = function( timer ) {
+ jQuery.timers.push( timer );
+ if ( timer() ) {
+ jQuery.fx.start();
+ } else {
+ jQuery.timers.pop();
+ }
+};
+
+jQuery.fx.interval = 13;
+
+jQuery.fx.start = function() {
+ if ( !timerId ) {
+ timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
+ }
+};
+
+jQuery.fx.stop = function() {
+ clearInterval( timerId );
+ timerId = null;
+};
+
+jQuery.fx.speeds = {
+ slow: 600,
+ fast: 200,
+ // Default speed
+ _default: 400
+};
+
+
+// Based off of the plugin by Clint Helfers, with permission.
+// http://blindsignals.com/index.php/2009/07/jquery-delay/
+jQuery.fn.delay = function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+};
+
+
+(function() {
+ var input = document.createElement( "input" ),
+ select = document.createElement( "select" ),
+ opt = select.appendChild( document.createElement( "option" ) );
+
+ input.type = "checkbox";
+
+ // Support: iOS<=5.1, Android<=4.2+
+ // Default value for a checkbox should be "on"
+ support.checkOn = input.value !== "";
+
+ // Support: IE<=11+
+ // Must access selectedIndex to make default options select
+ support.optSelected = opt.selected;
+
+ // Support: Android<=2.3
+ // Options inside disabled selects are incorrectly marked as disabled
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
+
+ // Support: IE<=11+
+ // An input loses its value after becoming a radio
+ input = document.createElement( "input" );
+ input.value = "t";
+ input.type = "radio";
+ support.radioValue = input.value === "t";
+})();
+
+
+var nodeHook, boolHook,
+ attrHandle = jQuery.expr.attrHandle;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return access( this, jQuery.attr, name, value, arguments.length > 1 );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ }
+});
+
+jQuery.extend({
+ attr: function( elem, name, value ) {
+ var hooks, ret,
+ nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === strundefined ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ // All attributes are lowercase
+ // Grab necessary hook if one is defined
+ if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+ name = name.toLowerCase();
+ hooks = jQuery.attrHooks[ name ] ||
+ ( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+
+ } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, value + "" );
+ return value;
+ }
+
+ } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ ret = jQuery.find.attr( elem, name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret == null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var name, propName,
+ i = 0,
+ attrNames = value && value.match( rnotwhite );
+
+ if ( attrNames && elem.nodeType === 1 ) {
+ while ( (name = attrNames[i++]) ) {
+ propName = jQuery.propFix[ name ] || name;
+
+ // Boolean attributes get special treatment (#10870)
+ if ( jQuery.expr.match.bool.test( name ) ) {
+ // Set corresponding property to false
+ elem[ propName ] = false;
+ }
+
+ elem.removeAttribute( name );
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ if ( !support.radioValue && value === "radio" &&
+ jQuery.nodeName( elem, "input" ) ) {
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ }
+ }
+});
+
+// Hooks for boolean attributes
+boolHook = {
+ set: function( elem, value, name ) {
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ elem.setAttribute( name, name );
+ }
+ return name;
+ }
+};
+jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
+ var getter = attrHandle[ name ] || jQuery.find.attr;
+
+ attrHandle[ name ] = function( elem, name, isXML ) {
+ var ret, handle;
+ if ( !isXML ) {
+ // Avoid an infinite loop by temporarily removing this function from the getter
+ handle = attrHandle[ name ];
+ attrHandle[ name ] = ret;
+ ret = getter( elem, name, isXML ) != null ?
+ name.toLowerCase() :
+ null;
+ attrHandle[ name ] = handle;
+ }
+ return ret;
+ };
+});
+
+
+
+
+var rfocusable = /^(?:input|select|textarea|button)$/i;
+
+jQuery.fn.extend({
+ prop: function( name, value ) {
+ return access( this, jQuery.prop, name, value, arguments.length > 1 );
+ },
+
+ removeProp: function( name ) {
+ return this.each(function() {
+ delete this[ jQuery.propFix[ name ] || name ];
+ });
+ }
+});
+
+jQuery.extend({
+ propFix: {
+ "for": "htmlFor",
+ "class": "className"
+ },
+
+ prop: function( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // Don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?
+ ret :
+ ( elem[ name ] = value );
+
+ } else {
+ return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ?
+ ret :
+ elem[ name ];
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ return elem.hasAttribute( "tabindex" ) || rfocusable.test( elem.nodeName ) || elem.href ?
+ elem.tabIndex :
+ -1;
+ }
+ }
+ }
+});
+
+if ( !support.optSelected ) {
+ jQuery.propHooks.selected = {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+ if ( parent && parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ return null;
+ }
+ };
+}
+
+jQuery.each([
+ "tabIndex",
+ "readOnly",
+ "maxLength",
+ "cellSpacing",
+ "cellPadding",
+ "rowSpan",
+ "colSpan",
+ "useMap",
+ "frameBorder",
+ "contentEditable"
+], function() {
+ jQuery.propFix[ this.toLowerCase() ] = this;
+});
+
+
+
+
+var rclass = /[\t\r\n\f]/g;
+
+jQuery.fn.extend({
+ addClass: function( value ) {
+ var classes, elem, cur, clazz, j, finalValue,
+ proceed = typeof value === "string" && value,
+ i = 0,
+ len = this.length;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call( this, j, this.className ) );
+ });
+ }
+
+ if ( proceed ) {
+ // The disjunction here is for better compressibility (see removeClass)
+ classes = ( value || "" ).match( rnotwhite ) || [];
+
+ for ( ; i < len; i++ ) {
+ elem = this[ i ];
+ cur = elem.nodeType === 1 && ( elem.className ?
+ ( " " + elem.className + " " ).replace( rclass, " " ) :
+ " "
+ );
+
+ if ( cur ) {
+ j = 0;
+ while ( (clazz = classes[j++]) ) {
+ if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
+ cur += clazz + " ";
+ }
+ }
+
+ // only assign if different to avoid unneeded rendering.
+ finalValue = jQuery.trim( cur );
+ if ( elem.className !== finalValue ) {
+ elem.className = finalValue;
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var classes, elem, cur, clazz, j, finalValue,
+ proceed = arguments.length === 0 || typeof value === "string" && value,
+ i = 0,
+ len = this.length;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call( this, j, this.className ) );
+ });
+ }
+ if ( proceed ) {
+ classes = ( value || "" ).match( rnotwhite ) || [];
+
+ for ( ; i < len; i++ ) {
+ elem = this[ i ];
+ // This expression is here for better compressibility (see addClass)
+ cur = elem.nodeType === 1 && ( elem.className ?
+ ( " " + elem.className + " " ).replace( rclass, " " ) :
+ ""
+ );
+
+ if ( cur ) {
+ j = 0;
+ while ( (clazz = classes[j++]) ) {
+ // Remove *all* instances
+ while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
+ cur = cur.replace( " " + clazz + " ", " " );
+ }
+ }
+
+ // Only assign if different to avoid unneeded rendering.
+ finalValue = value ? jQuery.trim( cur ) : "";
+ if ( elem.className !== finalValue ) {
+ elem.className = finalValue;
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value;
+
+ if ( typeof stateVal === "boolean" && type === "string" ) {
+ return stateVal ? this.addClass( value ) : this.removeClass( value );
+ }
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // Toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ classNames = value.match( rnotwhite ) || [];
+
+ while ( (className = classNames[ i++ ]) ) {
+ // Check each className given, space separated list
+ if ( self.hasClass( className ) ) {
+ self.removeClass( className );
+ } else {
+ self.addClass( className );
+ }
+ }
+
+ // Toggle whole class name
+ } else if ( type === strundefined || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ data_priv.set( this, "__className__", this.className );
+ }
+
+ // If the element has a class name or if we're passed `false`,
+ // then remove the whole classname (if there was one, the above saved it).
+ // Otherwise bring back whatever was previously saved (if anything),
+ // falling back to the empty string if nothing was stored.
+ this.className = this.className || value === false ? "" : data_priv.get( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ",
+ i = 0,
+ l = this.length;
+ for ( ; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+});
+
+
+
+
+var rreturn = /\r/g;
+
+jQuery.fn.extend({
+ val: function( value ) {
+ var hooks, ret, isFunction,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // Handle most common string cases
+ ret.replace(rreturn, "") :
+ // Handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var val;
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, jQuery( this ).val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+
+ } else if ( typeof val === "number" ) {
+ val += "";
+
+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map( val, function( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ var val = jQuery.find.attr( elem, "value" );
+ return val != null ?
+ val :
+ // Support: IE10-11+
+ // option.text throws exceptions (#14686, #14858)
+ jQuery.trim( jQuery.text( elem ) );
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, option,
+ options = elem.options,
+ index = elem.selectedIndex,
+ one = elem.type === "select-one" || index < 0,
+ values = one ? null : [],
+ max = one ? index + 1 : options.length,
+ i = index < 0 ?
+ max :
+ one ? index : 0;
+
+ // Loop through all the selected options
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // IE6-9 doesn't update selected after form reset (#2551)
+ if ( ( option.selected || i === index ) &&
+ // Don't return options that are disabled or in a disabled optgroup
+ ( support.optDisabled ? !option.disabled : option.getAttribute( "disabled" ) === null ) &&
+ ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var optionSet, option,
+ options = elem.options,
+ values = jQuery.makeArray( value ),
+ i = options.length;
+
+ while ( i-- ) {
+ option = options[ i ];
+ if ( (option.selected = jQuery.inArray( option.value, values ) >= 0) ) {
+ optionSet = true;
+ }
+ }
+
+ // Force browsers to behave consistently when non-matching value is set
+ if ( !optionSet ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ }
+});
+
+// Radios and checkboxes getter/setter
+jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+ }
+ }
+ };
+ if ( !support.checkOn ) {
+ jQuery.valHooks[ this ].get = function( elem ) {
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ };
+ }
+});
+
+
+
+
+// Return jQuery for attributes-only inclusion
+
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ return arguments.length > 0 ?
+ this.on( name, null, data, fn ) :
+ this.trigger( name );
+ };
+});
+
+jQuery.fn.extend({
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ },
+
+ bind: function( types, data, fn ) {
+ return this.on( types, null, data, fn );
+ },
+ unbind: function( types, fn ) {
+ return this.off( types, null, fn );
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.on( types, selector, data, fn );
+ },
+ undelegate: function( selector, types, fn ) {
+ // ( namespace ) or ( selector, types [, fn] )
+ return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
+ }
+});
+
+
+var nonce = jQuery.now();
+
+var rquery = (/\?/);
+
+
+
+// Support: Android 2.3
+// Workaround failure to string-cast null input
+jQuery.parseJSON = function( data ) {
+ return JSON.parse( data + "" );
+};
+
+
+// Cross-browser xml parsing
+jQuery.parseXML = function( data ) {
+ var xml, tmp;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+
+ // Support: IE9
+ try {
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data, "text/xml" );
+ } catch ( e ) {
+ xml = undefined;
+ }
+
+ if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+};
+
+
+var
+ rhash = /#.*$/,
+ rts = /([?&])_=[^&]*/,
+ rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
+ // #7653, #8125, #8152: local protocol detection
+ rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
+ rnoContent = /^(?:GET|HEAD)$/,
+ rprotocol = /^\/\//,
+ rurl = /^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,
+
+ /* Prefilters
+ * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+ * 2) These are called:
+ * - BEFORE asking for a transport
+ * - AFTER param serialization (s.data is a string if s.processData is true)
+ * 3) key is the dataType
+ * 4) the catchall symbol "*" can be used
+ * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+ */
+ prefilters = {},
+
+ /* Transports bindings
+ * 1) key is the dataType
+ * 2) the catchall symbol "*" can be used
+ * 3) selection will start with transport dataType and THEN go to "*" if needed
+ */
+ transports = {},
+
+ // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+ allTypes = "*/".concat( "*" ),
+
+ // Document location
+ ajaxLocation = window.location.href,
+
+ // Segment location into parts
+ ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+ // dataTypeExpression is optional and defaults to "*"
+ return function( dataTypeExpression, func ) {
+
+ if ( typeof dataTypeExpression !== "string" ) {
+ func = dataTypeExpression;
+ dataTypeExpression = "*";
+ }
+
+ var dataType,
+ i = 0,
+ dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];
+
+ if ( jQuery.isFunction( func ) ) {
+ // For each dataType in the dataTypeExpression
+ while ( (dataType = dataTypes[i++]) ) {
+ // Prepend if requested
+ if ( dataType[0] === "+" ) {
+ dataType = dataType.slice( 1 ) || "*";
+ (structure[ dataType ] = structure[ dataType ] || []).unshift( func );
+
+ // Otherwise append
+ } else {
+ (structure[ dataType ] = structure[ dataType ] || []).push( func );
+ }
+ }
+ }
+ };
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
+
+ var inspected = {},
+ seekingTransport = ( structure === transports );
+
+ function inspect( dataType ) {
+ var selected;
+ inspected[ dataType ] = true;
+ jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
+ var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
+ if ( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
+ options.dataTypes.unshift( dataTypeOrTransport );
+ inspect( dataTypeOrTransport );
+ return false;
+ } else if ( seekingTransport ) {
+ return !( selected = dataTypeOrTransport );
+ }
+ });
+ return selected;
+ }
+
+ return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+ var key, deep,
+ flatOptions = jQuery.ajaxSettings.flatOptions || {};
+
+ for ( key in src ) {
+ if ( src[ key ] !== undefined ) {
+ ( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];
+ }
+ }
+ if ( deep ) {
+ jQuery.extend( true, target, deep );
+ }
+
+ return target;
+}
+
+/* Handles responses to an ajax request:
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+ var ct, type, finalDataType, firstDataType,
+ contents = s.contents,
+ dataTypes = s.dataTypes;
+
+ // Remove auto dataType and get content-type in the process
+ while ( dataTypes[ 0 ] === "*" ) {
+ dataTypes.shift();
+ if ( ct === undefined ) {
+ ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
+ }
+ }
+
+ // Check if we're dealing with a known content-type
+ if ( ct ) {
+ for ( type in contents ) {
+ if ( contents[ type ] && contents[ type ].test( ct ) ) {
+ dataTypes.unshift( type );
+ break;
+ }
+ }
+ }
+
+ // Check to see if we have a response for the expected dataType
+ if ( dataTypes[ 0 ] in responses ) {
+ finalDataType = dataTypes[ 0 ];
+ } else {
+ // Try convertible dataTypes
+ for ( type in responses ) {
+ if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+ finalDataType = type;
+ break;
+ }
+ if ( !firstDataType ) {
+ firstDataType = type;
+ }
+ }
+ // Or just use first one
+ finalDataType = finalDataType || firstDataType;
+ }
+
+ // If we found a dataType
+ // We add the dataType to the list if needed
+ // and return the corresponding response
+ if ( finalDataType ) {
+ if ( finalDataType !== dataTypes[ 0 ] ) {
+ dataTypes.unshift( finalDataType );
+ }
+ return responses[ finalDataType ];
+ }
+}
+
+/* Chain conversions given the request and the original response
+ * Also sets the responseXXX fields on the jqXHR instance
+ */
+function ajaxConvert( s, response, jqXHR, isSuccess ) {
+ var conv2, current, conv, tmp, prev,
+ converters = {},
+ // Work with a copy of dataTypes in case we need to modify it for conversion
+ dataTypes = s.dataTypes.slice();
+
+ // Create converters map with lowercased keys
+ if ( dataTypes[ 1 ] ) {
+ for ( conv in s.converters ) {
+ converters[ conv.toLowerCase() ] = s.converters[ conv ];
+ }
+ }
+
+ current = dataTypes.shift();
+
+ // Convert to each sequential dataType
+ while ( current ) {
+
+ if ( s.responseFields[ current ] ) {
+ jqXHR[ s.responseFields[ current ] ] = response;
+ }
+
+ // Apply the dataFilter if provided
+ if ( !prev && isSuccess && s.dataFilter ) {
+ response = s.dataFilter( response, s.dataType );
+ }
+
+ prev = current;
+ current = dataTypes.shift();
+
+ if ( current ) {
+
+ // There's only work to do if current dataType is non-auto
+ if ( current === "*" ) {
+
+ current = prev;
+
+ // Convert response if prev dataType is non-auto and differs from current
+ } else if ( prev !== "*" && prev !== current ) {
+
+ // Seek a direct converter
+ conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+ // If none found, seek a pair
+ if ( !conv ) {
+ for ( conv2 in converters ) {
+
+ // If conv2 outputs current
+ tmp = conv2.split( " " );
+ if ( tmp[ 1 ] === current ) {
+
+ // If prev can be converted to accepted input
+ conv = converters[ prev + " " + tmp[ 0 ] ] ||
+ converters[ "* " + tmp[ 0 ] ];
+ if ( conv ) {
+ // Condense equivalence converters
+ if ( conv === true ) {
+ conv = converters[ conv2 ];
+
+ // Otherwise, insert the intermediate dataType
+ } else if ( converters[ conv2 ] !== true ) {
+ current = tmp[ 0 ];
+ dataTypes.unshift( tmp[ 1 ] );
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ // Apply converter (if not an equivalence)
+ if ( conv !== true ) {
+
+ // Unless errors are allowed to bubble, catch and return them
+ if ( conv && s[ "throws" ] ) {
+ response = conv( response );
+ } else {
+ try {
+ response = conv( response );
+ } catch ( e ) {
+ return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return { state: "success", data: response };
+}
+
+jQuery.extend({
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+ etag: {},
+
+ ajaxSettings: {
+ url: ajaxLocation,
+ type: "GET",
+ isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+ global: true,
+ processData: true,
+ async: true,
+ contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+ /*
+ timeout: 0,
+ data: null,
+ dataType: null,
+ username: null,
+ password: null,
+ cache: null,
+ throws: false,
+ traditional: false,
+ headers: {},
+ */
+
+ accepts: {
+ "*": allTypes,
+ text: "text/plain",
+ html: "text/html",
+ xml: "application/xml, text/xml",
+ json: "application/json, text/javascript"
+ },
+
+ contents: {
+ xml: /xml/,
+ html: /html/,
+ json: /json/
+ },
+
+ responseFields: {
+ xml: "responseXML",
+ text: "responseText",
+ json: "responseJSON"
+ },
+
+ // Data converters
+ // Keys separate source (or catchall "*") and destination types with a single space
+ converters: {
+
+ // Convert anything to text
+ "* text": String,
+
+ // Text to html (true = no transformation)
+ "text html": true,
+
+ // Evaluate text as a json expression
+ "text json": jQuery.parseJSON,
+
+ // Parse text as xml
+ "text xml": jQuery.parseXML
+ },
+
+ // For options that shouldn't be deep extended:
+ // you can add your own custom options here if
+ // and when you create one that shouldn't be
+ // deep extended (see ajaxExtend)
+ flatOptions: {
+ url: true,
+ context: true
+ }
+ },
+
+ // Creates a full fledged settings object into target
+ // with both ajaxSettings and settings fields.
+ // If target is omitted, writes into ajaxSettings.
+ ajaxSetup: function( target, settings ) {
+ return settings ?
+
+ // Building a settings object
+ ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
+
+ // Extending ajaxSettings
+ ajaxExtend( jQuery.ajaxSettings, target );
+ },
+
+ ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+ ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+ // Main method
+ ajax: function( url, options ) {
+
+ // If url is an object, simulate pre-1.5 signature
+ if ( typeof url === "object" ) {
+ options = url;
+ url = undefined;
+ }
+
+ // Force options to be an object
+ options = options || {};
+
+ var transport,
+ // URL without anti-cache param
+ cacheURL,
+ // Response headers
+ responseHeadersString,
+ responseHeaders,
+ // timeout handle
+ timeoutTimer,
+ // Cross-domain detection vars
+ parts,
+ // To know if global events are to be dispatched
+ fireGlobals,
+ // Loop variable
+ i,
+ // Create the final options object
+ s = jQuery.ajaxSetup( {}, options ),
+ // Callbacks context
+ callbackContext = s.context || s,
+ // Context for global events is callbackContext if it is a DOM node or jQuery collection
+ globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
+ jQuery( callbackContext ) :
+ jQuery.event,
+ // Deferreds
+ deferred = jQuery.Deferred(),
+ completeDeferred = jQuery.Callbacks("once memory"),
+ // Status-dependent callbacks
+ statusCode = s.statusCode || {},
+ // Headers (they are sent all at once)
+ requestHeaders = {},
+ requestHeadersNames = {},
+ // The jqXHR state
+ state = 0,
+ // Default abort message
+ strAbort = "canceled",
+ // Fake xhr
+ jqXHR = {
+ readyState: 0,
+
+ // Builds headers hashtable if needed
+ getResponseHeader: function( key ) {
+ var match;
+ if ( state === 2 ) {
+ if ( !responseHeaders ) {
+ responseHeaders = {};
+ while ( (match = rheaders.exec( responseHeadersString )) ) {
+ responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+ }
+ }
+ match = responseHeaders[ key.toLowerCase() ];
+ }
+ return match == null ? null : match;
+ },
+
+ // Raw string
+ getAllResponseHeaders: function() {
+ return state === 2 ? responseHeadersString : null;
+ },
+
+ // Caches the header
+ setRequestHeader: function( name, value ) {
+ var lname = name.toLowerCase();
+ if ( !state ) {
+ name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+ requestHeaders[ name ] = value;
+ }
+ return this;
+ },
+
+ // Overrides response content-type header
+ overrideMimeType: function( type ) {
+ if ( !state ) {
+ s.mimeType = type;
+ }
+ return this;
+ },
+
+ // Status-dependent callbacks
+ statusCode: function( map ) {
+ var code;
+ if ( map ) {
+ if ( state < 2 ) {
+ for ( code in map ) {
+ // Lazy-add the new callback in a way that preserves old ones
+ statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
+ }
+ } else {
+ // Execute the appropriate callbacks
+ jqXHR.always( map[ jqXHR.status ] );
+ }
+ }
+ return this;
+ },
+
+ // Cancel the request
+ abort: function( statusText ) {
+ var finalText = statusText || strAbort;
+ if ( transport ) {
+ transport.abort( finalText );
+ }
+ done( 0, finalText );
+ return this;
+ }
+ };
+
+ // Attach deferreds
+ deferred.promise( jqXHR ).complete = completeDeferred.add;
+ jqXHR.success = jqXHR.done;
+ jqXHR.error = jqXHR.fail;
+
+ // Remove hash character (#7531: and string promotion)
+ // Add protocol if not provided (prefilters might expect it)
+ // Handle falsy url in the settings object (#10093: consistency with old signature)
+ // We also use the url parameter if available
+ s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" )
+ .replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+ // Alias method option to type as per ticket #12004
+ s.type = options.method || options.type || s.method || s.type;
+
+ // Extract dataTypes list
+ s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ];
+
+ // A cross-domain request is in order when we have a protocol:host:port mismatch
+ if ( s.crossDomain == null ) {
+ parts = rurl.exec( s.url.toLowerCase() );
+ s.crossDomain = !!( parts &&
+ ( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
+ ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? "80" : "443" ) ) !==
+ ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? "80" : "443" ) ) )
+ );
+ }
+
+ // Convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
+
+ // Apply prefilters
+ inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+ // If request was aborted inside a prefilter, stop there
+ if ( state === 2 ) {
+ return jqXHR;
+ }
+
+ // We can fire global events as of now if asked to
+ // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
+ fireGlobals = jQuery.event && s.global;
+
+ // Watch for a new set of requests
+ if ( fireGlobals && jQuery.active++ === 0 ) {
+ jQuery.event.trigger("ajaxStart");
+ }
+
+ // Uppercase the type
+ s.type = s.type.toUpperCase();
+
+ // Determine if request has content
+ s.hasContent = !rnoContent.test( s.type );
+
+ // Save the URL in case we're toying with the If-Modified-Since
+ // and/or If-None-Match header later on
+ cacheURL = s.url;
+
+ // More options handling for requests with no content
+ if ( !s.hasContent ) {
+
+ // If data is available, append data to url
+ if ( s.data ) {
+ cacheURL = ( s.url += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
+ // #9682: remove data so that it's not used in an eventual retry
+ delete s.data;
+ }
+
+ // Add anti-cache in url if needed
+ if ( s.cache === false ) {
+ s.url = rts.test( cacheURL ) ?
+
+ // If there is already a '_' parameter, set its value
+ cacheURL.replace( rts, "$1_=" + nonce++ ) :
+
+ // Otherwise add one to the end
+ cacheURL + ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + nonce++;
+ }
+ }
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ if ( jQuery.lastModified[ cacheURL ] ) {
+ jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
+ }
+ if ( jQuery.etag[ cacheURL ] ) {
+ jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
+ }
+ }
+
+ // Set the correct header, if data is being sent
+ if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+ jqXHR.setRequestHeader( "Content-Type", s.contentType );
+ }
+
+ // Set the Accepts header for the server, depending on the dataType
+ jqXHR.setRequestHeader(
+ "Accept",
+ s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+ s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+ s.accepts[ "*" ]
+ );
+
+ // Check for headers option
+ for ( i in s.headers ) {
+ jqXHR.setRequestHeader( i, s.headers[ i ] );
+ }
+
+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+ // Abort if not done already and return
+ return jqXHR.abort();
+ }
+
+ // Aborting is no longer a cancellation
+ strAbort = "abort";
+
+ // Install callbacks on deferreds
+ for ( i in { success: 1, error: 1, complete: 1 } ) {
+ jqXHR[ i ]( s[ i ] );
+ }
+
+ // Get transport
+ transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+ // If no transport, we auto-abort
+ if ( !transport ) {
+ done( -1, "No Transport" );
+ } else {
+ jqXHR.readyState = 1;
+
+ // Send global event
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+ }
+ // Timeout
+ if ( s.async && s.timeout > 0 ) {
+ timeoutTimer = setTimeout(function() {
+ jqXHR.abort("timeout");
+ }, s.timeout );
+ }
+
+ try {
+ state = 1;
+ transport.send( requestHeaders, done );
+ } catch ( e ) {
+ // Propagate exception as error if not done
+ if ( state < 2 ) {
+ done( -1, e );
+ // Simply rethrow otherwise
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ // Callback for when everything is done
+ function done( status, nativeStatusText, responses, headers ) {
+ var isSuccess, success, error, response, modified,
+ statusText = nativeStatusText;
+
+ // Called once
+ if ( state === 2 ) {
+ return;
+ }
+
+ // State is "done" now
+ state = 2;
+
+ // Clear timeout if it exists
+ if ( timeoutTimer ) {
+ clearTimeout( timeoutTimer );
+ }
+
+ // Dereference transport for early garbage collection
+ // (no matter how long the jqXHR object will be used)
+ transport = undefined;
+
+ // Cache response headers
+ responseHeadersString = headers || "";
+
+ // Set readyState
+ jqXHR.readyState = status > 0 ? 4 : 0;
+
+ // Determine if successful
+ isSuccess = status >= 200 && status < 300 || status === 304;
+
+ // Get response data
+ if ( responses ) {
+ response = ajaxHandleResponses( s, jqXHR, responses );
+ }
+
+ // Convert no matter what (that way responseXXX fields are always set)
+ response = ajaxConvert( s, response, jqXHR, isSuccess );
+
+ // If successful, handle type chaining
+ if ( isSuccess ) {
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ modified = jqXHR.getResponseHeader("Last-Modified");
+ if ( modified ) {
+ jQuery.lastModified[ cacheURL ] = modified;
+ }
+ modified = jqXHR.getResponseHeader("etag");
+ if ( modified ) {
+ jQuery.etag[ cacheURL ] = modified;
+ }
+ }
+
+ // if no content
+ if ( status === 204 || s.type === "HEAD" ) {
+ statusText = "nocontent";
+
+ // if not modified
+ } else if ( status === 304 ) {
+ statusText = "notmodified";
+
+ // If we have data, let's convert it
+ } else {
+ statusText = response.state;
+ success = response.data;
+ error = response.error;
+ isSuccess = !error;
+ }
+ } else {
+ // Extract error from statusText and normalize for non-aborts
+ error = statusText;
+ if ( status || !statusText ) {
+ statusText = "error";
+ if ( status < 0 ) {
+ status = 0;
+ }
+ }
+ }
+
+ // Set data for the fake xhr object
+ jqXHR.status = status;
+ jqXHR.statusText = ( nativeStatusText || statusText ) + "";
+
+ // Success/Error
+ if ( isSuccess ) {
+ deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+ } else {
+ deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+ }
+
+ // Status-dependent callbacks
+ jqXHR.statusCode( statusCode );
+ statusCode = undefined;
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
+ [ jqXHR, s, isSuccess ? success : error ] );
+ }
+
+ // Complete
+ completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+ // Handle the global AJAX counter
+ if ( !( --jQuery.active ) ) {
+ jQuery.event.trigger("ajaxStop");
+ }
+ }
+ }
+
+ return jqXHR;
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get( url, data, callback, "json" );
+ },
+
+ getScript: function( url, callback ) {
+ return jQuery.get( url, undefined, callback, "script" );
+ }
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+ jQuery[ method ] = function( url, data, callback, type ) {
+ // Shift arguments if data argument was omitted
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = undefined;
+ }
+
+ return jQuery.ajax({
+ url: url,
+ type: method,
+ dataType: type,
+ data: data,
+ success: callback
+ });
+ };
+});
+
+
+jQuery._evalUrl = function( url ) {
+ return jQuery.ajax({
+ url: url,
+ type: "GET",
+ dataType: "script",
+ async: false,
+ global: false,
+ "throws": true
+ });
+};
+
+
+jQuery.fn.extend({
+ wrapAll: function( html ) {
+ var wrap;
+
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).wrapAll( html.call(this, i) );
+ });
+ }
+
+ if ( this[ 0 ] ) {
+
+ // The elements to wrap the target around
+ wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
+
+ if ( this[ 0 ].parentNode ) {
+ wrap.insertBefore( this[ 0 ] );
+ }
+
+ wrap.map(function() {
+ var elem = this;
+
+ while ( elem.firstElementChild ) {
+ elem = elem.firstElementChild;
+ }
+
+ return elem;
+ }).append( this );
+ }
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).wrapInner( html.call(this, i) );
+ });
+ }
+
+ return this.each(function() {
+ var self = jQuery( this ),
+ contents = self.contents();
+
+ if ( contents.length ) {
+ contents.wrapAll( html );
+
+ } else {
+ self.append( html );
+ }
+ });
+ },
+
+ wrap: function( html ) {
+ var isFunction = jQuery.isFunction( html );
+
+ return this.each(function( i ) {
+ jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+ });
+ },
+
+ unwrap: function() {
+ return this.parent().each(function() {
+ if ( !jQuery.nodeName( this, "body" ) ) {
+ jQuery( this ).replaceWith( this.childNodes );
+ }
+ }).end();
+ }
+});
+
+
+jQuery.expr.filters.hidden = function( elem ) {
+ // Support: Opera <= 12.12
+ // Opera reports offsetWidths and offsetHeights less than zero on some elements
+ return elem.offsetWidth <= 0 && elem.offsetHeight <= 0;
+};
+jQuery.expr.filters.visible = function( elem ) {
+ return !jQuery.expr.filters.hidden( elem );
+};
+
+
+
+
+var r20 = /%20/g,
+ rbracket = /\[\]$/,
+ rCRLF = /\r?\n/g,
+ rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
+ rsubmittable = /^(?:input|select|textarea|keygen)/i;
+
+function buildParams( prefix, obj, traditional, add ) {
+ var name;
+
+ if ( jQuery.isArray( obj ) ) {
+ // Serialize array item.
+ jQuery.each( obj, function( i, v ) {
+ if ( traditional || rbracket.test( prefix ) ) {
+ // Treat each array item as a scalar.
+ add( prefix, v );
+
+ } else {
+ // Item is non-scalar (array or object), encode its numeric index.
+ buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
+ }
+ });
+
+ } else if ( !traditional && jQuery.type( obj ) === "object" ) {
+ // Serialize object item.
+ for ( name in obj ) {
+ buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+ }
+
+ } else {
+ // Serialize scalar item.
+ add( prefix, obj );
+ }
+}
+
+// Serialize an array of form elements or a set of
+// key/values into a query string
+jQuery.param = function( a, traditional ) {
+ var prefix,
+ s = [],
+ add = function( key, value ) {
+ // If value is a function, invoke it and return its value
+ value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
+ s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+ };
+
+ // Set traditional to true for jQuery <= 1.3.2 behavior.
+ if ( traditional === undefined ) {
+ traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
+ }
+
+ // If an array was passed in, assume that it is an array of form elements.
+ if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+ // Serialize the form elements
+ jQuery.each( a, function() {
+ add( this.name, this.value );
+ });
+
+ } else {
+ // If traditional, encode the "old" way (the way 1.3.2 or older
+ // did it), otherwise encode params recursively.
+ for ( prefix in a ) {
+ buildParams( prefix, a[ prefix ], traditional, add );
+ }
+ }
+
+ // Return the resulting serialization
+ return s.join( "&" ).replace( r20, "+" );
+};
+
+jQuery.fn.extend({
+ serialize: function() {
+ return jQuery.param( this.serializeArray() );
+ },
+ serializeArray: function() {
+ return this.map(function() {
+ // Can add propHook for "elements" to filter or add form elements
+ var elements = jQuery.prop( this, "elements" );
+ return elements ? jQuery.makeArray( elements ) : this;
+ })
+ .filter(function() {
+ var type = this.type;
+
+ // Use .is( ":disabled" ) so that fieldset[disabled] works
+ return this.name && !jQuery( this ).is( ":disabled" ) &&
+ rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
+ ( this.checked || !rcheckableType.test( type ) );
+ })
+ .map(function( i, elem ) {
+ var val = jQuery( this ).val();
+
+ return val == null ?
+ null :
+ jQuery.isArray( val ) ?
+ jQuery.map( val, function( val ) {
+ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }) :
+ { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }).get();
+ }
+});
+
+
+jQuery.ajaxSettings.xhr = function() {
+ try {
+ return new XMLHttpRequest();
+ } catch( e ) {}
+};
+
+var xhrId = 0,
+ xhrCallbacks = {},
+ xhrSuccessStatus = {
+ // file protocol always yields status code 0, assume 200
+ 0: 200,
+ // Support: IE9
+ // #1450: sometimes IE returns 1223 when it should be 204
+ 1223: 204
+ },
+ xhrSupported = jQuery.ajaxSettings.xhr();
+
+// Support: IE9
+// Open requests must be manually aborted on unload (#5280)
+// See https://support.microsoft.com/kb/2856746 for more info
+if ( window.attachEvent ) {
+ window.attachEvent( "onunload", function() {
+ for ( var key in xhrCallbacks ) {
+ xhrCallbacks[ key ]();
+ }
+ });
+}
+
+support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
+support.ajax = xhrSupported = !!xhrSupported;
+
+jQuery.ajaxTransport(function( options ) {
+ var callback;
+
+ // Cross domain only allowed if supported through XMLHttpRequest
+ if ( support.cors || xhrSupported && !options.crossDomain ) {
+ return {
+ send: function( headers, complete ) {
+ var i,
+ xhr = options.xhr(),
+ id = ++xhrId;
+
+ xhr.open( options.type, options.url, options.async, options.username, options.password );
+
+ // Apply custom fields if provided
+ if ( options.xhrFields ) {
+ for ( i in options.xhrFields ) {
+ xhr[ i ] = options.xhrFields[ i ];
+ }
+ }
+
+ // Override mime type if needed
+ if ( options.mimeType && xhr.overrideMimeType ) {
+ xhr.overrideMimeType( options.mimeType );
+ }
+
+ // X-Requested-With header
+ // For cross-domain requests, seeing as conditions for a preflight are
+ // akin to a jigsaw puzzle, we simply never set it to be sure.
+ // (it can always be set on a per-request basis or even using ajaxSetup)
+ // For same-domain requests, won't change header if already provided.
+ if ( !options.crossDomain && !headers["X-Requested-With"] ) {
+ headers["X-Requested-With"] = "XMLHttpRequest";
+ }
+
+ // Set headers
+ for ( i in headers ) {
+ xhr.setRequestHeader( i, headers[ i ] );
+ }
+
+ // Callback
+ callback = function( type ) {
+ return function() {
+ if ( callback ) {
+ delete xhrCallbacks[ id ];
+ callback = xhr.onload = xhr.onerror = null;
+
+ if ( type === "abort" ) {
+ xhr.abort();
+ } else if ( type === "error" ) {
+ complete(
+ // file: protocol always yields status 0; see #8605, #14207
+ xhr.status,
+ xhr.statusText
+ );
+ } else {
+ complete(
+ xhrSuccessStatus[ xhr.status ] || xhr.status,
+ xhr.statusText,
+ // Support: IE9
+ // Accessing binary-data responseText throws an exception
+ // (#11426)
+ typeof xhr.responseText === "string" ? {
+ text: xhr.responseText
+ } : undefined,
+ xhr.getAllResponseHeaders()
+ );
+ }
+ }
+ };
+ };
+
+ // Listen to events
+ xhr.onload = callback();
+ xhr.onerror = callback("error");
+
+ // Create the abort callback
+ callback = xhrCallbacks[ id ] = callback("abort");
+
+ try {
+ // Do send the request (this may raise an exception)
+ xhr.send( options.hasContent && options.data || null );
+ } catch ( e ) {
+ // #14683: Only rethrow if this hasn't been notified as an error yet
+ if ( callback ) {
+ throw e;
+ }
+ }
+ },
+
+ abort: function() {
+ if ( callback ) {
+ callback();
+ }
+ }
+ };
+ }
+});
+
+
+
+
+// Install script dataType
+jQuery.ajaxSetup({
+ accepts: {
+ script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+ },
+ contents: {
+ script: /(?:java|ecma)script/
+ },
+ converters: {
+ "text script": function( text ) {
+ jQuery.globalEval( text );
+ return text;
+ }
+ }
+});
+
+// Handle cache's special case and crossDomain
+jQuery.ajaxPrefilter( "script", function( s ) {
+ if ( s.cache === undefined ) {
+ s.cache = false;
+ }
+ if ( s.crossDomain ) {
+ s.type = "GET";
+ }
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function( s ) {
+ // This transport only deals with cross domain requests
+ if ( s.crossDomain ) {
+ var script, callback;
+ return {
+ send: function( _, complete ) {
+ script = jQuery("';
+ })
+ .replace(/]*data-ke-noscript-attr="([^"]*)"[^>]*>([\s\S]*?)<\/div>/ig, function(full, attr, code) {
+ return '
' + unescape(code) + ' ';
+ })
+ .replace(/(<[^>]*)data-ke-src="([^"]*)"([^>]*>)/ig, function(full, start, src, end) {
+ full = full.replace(/(\s+(?:href|src)=")[^"]*(")/i, function($0, $1, $2) {
+ return $1 + _unescape(src) + $2;
+ });
+ full = full.replace(/\s+data-ke-src="[^"]*"/i, '');
+ return full;
+ })
+ .replace(/(<[^>]+\s)data-ke-(on\w+="[^"]*"[^>]*>)/ig, function(full, start, end) {
+ return start + end;
+ });
+ });
+ self.beforeSetHtml(function(html) {
+ if (_IE && _V <= 8) {
+ html = html.replace(/
]*>|<(select|button)[^>]*>[\s\S]*?<\/\1>/ig, function(full) {
+ var attrs = _getAttrList(full);
+ var styles = _getCssList(attrs.style || '');
+ if (styles.display == 'none') {
+ return '
';
+ }
+ return full;
+ });
+ }
+ return html.replace(/
]*type="([^"]+)"[^>]*>(?:<\/embed>)?/ig, function(full) {
+ var attrs = _getAttrList(full);
+ attrs.src = _undef(attrs.src, '');
+ attrs.width = _undef(attrs.width, 0);
+ attrs.height = _undef(attrs.height, 0);
+ return _mediaImg(self.themesPath + 'common/blank.gif', attrs);
+ })
+ .replace(/]*name="([^"]+)"[^>]*>(?:<\/a>)?/ig, function(full) {
+ var attrs = _getAttrList(full);
+ if (attrs.href !== undefined) {
+ return full;
+ }
+ return ' ';
+ })
+ .replace(/',
+ '',
+ '',
+ '',
+ '
',
+ '