除新人区外,看帖均消费积分,请认真发帖与回帖获得积分,新人区外灌水三次永封。QQ:9901259赠积分。

【转自crsky】PHPWind 源码解析

前阵子心血来潮想去看看PHPWIND的源码结果到处都找不到参考
只能自己动手了
本只打算看一个global.php文件的
结果为了看懂这一个文件牵扯出了58个文件需要查看
花了差不多半个星期的业余时间
结合php手册的说明,以及自己对源码的分析理解
总算在今天晚上全部看完
以在源文件中添加注释的方法写入源码分析
做人要厚道哦,转贴请注明 霏凡论坛 claudedb 原创
注:
1、本源码分析针对PHPWind 4.3.2 060428更新版做出
2、除部分文件外,多数文件均可直接使用,不影响论坛的正常运行
3、本次仅对global.php相关的文件进行解析说明
4、下面只贴出其中几个重要文件的源码分析,其余文件请自行下载附件查看
  基本上看懂这些文件,可以对phpwind论坛的运行机制有个大概的了解
5、欢迎大家指出文中的错误,毕竟我不是PHPWind的作者,肯定会有理解上的偏差

Quote:
下面对代码中的颜色使用作个说明
保留关键字
函数
类型
字符串
注释
括号     
数字
运算符
预定义变量
PHP开始结束标签


global.php

Quote:

<?php
/**
*
* Copyright (c) 2003-06 PHPWind.net. All rights reserved.
* Support : http://www.phpwind.net
* This software is the PRoprietary information of PHPWind.com.
*
*/
//此文件内容为全局变量和公用函数

//定义只有运行错误和语法编译错误才会显示错误信息

error_reporting(E_ERROR | E_PARSE);

//设置php.ini中 magic_quotes_runtime 选项值为0
//若 magic_quotes_runtime 打开时,所有外部引入的数据库资料或者文件等等都会自动转为含有反斜线溢出字符的资料。
//0表示关闭此功能

set_magic_quotes_runtime(0);

//microtime() 当前 Unix 时间戳以及微秒数。本函数仅在支持 gettimeofday() 系统调用的操作系统下可用。
//如果调用时不带可选参数,本函数以 "msec sec" 的格式返回一个字符串,其中 sec 是自 Unix 纪元(0:00:00 January 1, 1970 GMT)起到现在的秒数,msec 是微秒部分。字符串的两部分都是以秒为单位返回的。

//用$t_array变量记录microtime()函数产生的msec和sec,此时$t_array代表数组

$t_array = explode(' ',microtime());
//$P_S_T记录当前时间秒数
$P_S_T = $t_array[0] + $t_array[1];

//判断__FILE__是否为空,返回路径值,并定义为D_P和R_P
//其中__FILE__文件的完整绝对路径和文件名
//D_P和R_P的区别在于,当论坛使用二级目录设置时,D_P用于分论坛,R_P用于记录总论坛目录的绝对路径
//具体参考PHPWind论坛关于二级目录方面的说明
//在没有使用这方面设置的时候这两个值相等

define('D_P',__FILE__ ? getdirname(__FILE__).'/' : './');
define('R_P',D_P);

//销毁以下变量
unset($_ENV,$HTTP_ENV_VARS,$_REQUEST,$HTTP_POST_VARS,$HTTP_GET_VARS,$HTTP_POST_FILES,$HTTP_Cookie_VARS);

//get_magic_quotes_gpc函数取得 PHP 环境配置的变量 magic_quotes_gpc (GPC, Get/Post/Cookie) 值。返回 0 表示关闭本功能;返回 1 表示本功能打开。当 magic_quotes_gpc 打开时,所有的 ' (单引号), " (双引号), \ (反斜线) and 空字符会自动转为含有反斜线的溢出字符。
//在magic_quotes_gpc功能关闭的情况下,对$_POST、$_GET、$_COOKIE中的特殊字符转义处理

if(!get_magic_quotes_gpc()){
  //$_POST 经由 HTTP POST 方法提交至脚本的变量。
  Add_S($_POST);
  //$_GET 经由 URL 请求提交至脚本的变量。
  Add_S($_GET);
  //$_FILES 通过 HTTP POST 方法传递的已上传文件项目组成的数组。
  Add_S($_FILES);
  //$_COOKIE 经由 HTTP Cookies 方法提交至脚本的变量。
  Add_S($_COOKIE);
}

//$_SERVER 变量由 web 服务器设定或者直接与当前脚本的执行环境相关联。
//如果服务器变量中存在HTTP_X_FORWARDED_FOR,则从中取得客户端ip,如果没有则到HTTP_CLIENT_IP中取,都没有则到REMOTE_ADDR中取
//'HTTP_CLIENT_IP','HTTP_X_FORWARDED_FOR'是代理的IP
//这些IP头消息未必能够取得到(因为不同的浏览器不同的网络设备,可能发不同的IP头消息).所以PHP就尝试把每个IP头消息判断一下,若有,则取其中的一个.
//$c_agentip记录是否为代理ip

if($_SERVER['HTTP_X_FORWARDED_FOR']){
  $onlineip = $_SERVER['HTTP_X_FORWARDED_FOR'];
  $c_agentip=1;
}elseif($_SERVER['HTTP_CLIENT_IP']){
  $onlineip = $_SERVER['HTTP_CLIENT_IP'];
  $c_agentip=1;
}else{
  $onlineip = $_SERVER['REMOTE_ADDR'];
  $c_agentip=0;
}

//str_replace 用指定的字符串替换在被搜索的字符串中找到的字符串;
//第一个,要搜索的字符串,第二个为替换为的字符串,第三个被搜索的字符串
//取得客户端ip值

$onlineip = substrs(str_replace("\n",'',$onlineip),16);
//返回自从 Unix 纪元(格林威治时间 1970 年 1 月 1 日 00:00:00)到当前时间的秒数。
$timestamp= time();
//reqUIre_once require_once() 语句在脚本执行期间包含并运行指定文件。此行为和 require() 语句类似,
//唯一区别是如果该文件中的代码已经被包含了,则不会再次包含.应该用于在脚本执行期间同一个文件有可能被
//包含超过一次的情况下,想确保它只被包含一次以避免函数重定义,变量重新赋值等问题。
//包含require/defend.php文件,如果其中有重复包含,略过

require_once(R_P.'require/defend.php');

//根据时区设置$db_cvtime取得正确的时间$timestamp
$db_cvtime != 0 && $timestamp += $db_cvtime*60;

//如果论坛运行在DEBUG模式下
if($db_debug){
  //定义显示所有错误除了运行NOTICE错误
  error_reporting(E_ALL ^ E_NOTICE);
}
//PHPWind论坛版本变量
$wind_version = "4.3.2";
//在线列表大小值
$db_olsize = 96;
//静态htm缓存目录名
$htm dir = 'htm_data';

//'PHP_SELF' 当前正在执行脚本的文件名,与 document root 相关
//'SCRIPT_NAME' 当前执行脚本的绝对路径名,注: 如果脚本在 CLI 中被执行,作为相对路径,例如
//file.php 或 ../file.php,$_SERVER['SCRIPT_FILENAME'] 将包含用户指定的相对路径。
//'QUERY_STRING' 查询(query)的字符串(URL 中第一个问号 ? 之后的内容)。
//如果PHP_SELF(当前正在执行脚本的文件名)为空,则设置其为SCRIPT_NAME(包含当前脚本的路径)

!$_SERVER['PHP_SELF'] && $_SERVER['PHP_SELF']=$_SERVER['SCRIPT_NAME'];
//取得请求url完整的路径保存在$REQUEST_URI变量中
$REQUEST_URI = $_SERVER['PHP_SELF'].'?'.$_SERVER['QUERY_STRING'];
//判断当前执行文件名中是否包含静态目录$db_dir,如果包含,则
if(strpos($_SERVER['PHP_SELF'],$db_dir)!==false){
  //strpos 查找字符串第一次出现的位置,第一个为被查找的字符串,第二个为要查找的字符串。返回值为0,或
  //者被查找的字符第一次出现的数字位置的值;
  //取到静态目录前

  $tmp=substr($_SERVER['PHP_SELF'],0,strpos($_SERVER['PHP_SELF'],$db_dir));
}else{
  //取全部
  $tmp=$_SERVER['PHP_SELF'];
}
//HTTP_HOST-当前请求的 Host: 头信息的内容
//设置论坛地址

$db_bbsurl="http://$_SERVER[HTTP_HOST]".substr($tmp,0,strrpos($tmp,'/'));

//(int) 强制类型转换为整型
//定义整型变量$fid用于记录板块id

$fid = (int)$fid;
//定义整型变量$tid用于记录帖子id
$tid = (int)$tid;
//初始化附件保存目录$attachname及js脚本路径$js_path
$attachname = $js_path = '';
//包含data/bbscache/dbset.php文件,如果其中有重复包含,略过
require_once(D_P.'data/bbscache/dbset.php');

//ob_start("ob_gzhandler");是一种压缩技术
//ob_start();仅仅启动缓冲 并没有压缩输出
//ob_start 用来 打开输出缓冲区。 当缓冲区激活时,所有来自PHP程序的非文件头信息均不会发送,而是保存在内部缓冲区。为了输出缓冲区的内容,可以使用ob_end_flush()或者使用ob_end_clean()来输出缓冲区的内容。
//是否允许GZIP 压缩输出,允许使用ob_gzhandler压缩技术,否则只启动缓冲

$db_obstart == 1 ? ob_start('ob_gzhandler') : ob_start();

//如果使用跨台固定图片链,则图片路径为$db_http值,否则使用$pICPath图片保存目录
$imgpath = $db_http != 'N' ? $db_http : $piCPAth;
//如果使用远程附件连接设置,则附件路径为$db_attachurl,否则使用$attachname附件保存目录
$attachpath = $db_attachurl != 'N' ? $db_attachurl : $attachname;
//本地图片路径
$imgdir = R_P.$picpath;
//本地附件路径
$attachdir = R_P.$attachname;

//如果D_P和R_P不相等并且使用了跨台固定图片链,即使用了二级目录功能
if(D_P != R_P && $db_http != 'N'){
  //取总站论坛地址
  $R_url=substr($db_http,-1)=='/' ? substr($db_http,0,-1) : $db_http;
  $R_url=substr($R_url,0,strrpos($R_url,'/'));
}else{
  //总站论坛地址即全局变量$db_bbsurl中的论坛地址值
  $R_url=$db_bbsurl;
}

//如果cookie中上次访问变量lastvisIT值是否为空
if(GetCookie('lastvisit')){
  //取cookie中的上次访问变量lastvisit值,以"\t"分割并分别赋于$c_oltime,$lastvisit,$lastpath
  //$c_oltime-用户在线时间,$lastvisit-上次访问时间,$lastpath-上次访问路径

  list($c_oltime,$lastvisit,$lastpath) = explode("\t",GetCookie('lastvisit'));
  //判断现在和上次访问之间的时间差是否小于论坛设定的在线用户时限,如果小于,则用户此次在线时间累加相应时间数值
  ($onbbstime=$timestamp-$lastvisit)<$db_onlinetime && $c_oltime+=$onbbstime;
}else{
  $lastvisit=$lastpath='';
  $c_oltime=0;
}

//取在线偏移cookie(在线列表文件中的偏移量)
$ol_offset = GetCookie('ol_offset');

//取风格cookie
$skinco = GetCookie('skinco');
//若刷新预防时间不等于0
if ($db_refreshtime!=0){
  //$REQUEST_URI 访问此页面所需的 URI。
  //如果$REQUEST_URI与上次最后访问的路径相同,且与上次访问的时间差小于论坛设定的刷新预防时间

  if($REQUEST_URI==$lastpath && $onbbstime<$db_refreshtime){
    //如果Cookie中没有winduser即用户的记录,设置$groupid为guest即当前用户归于游客组
    !GetCookie('winduser') && $groupid='guest';
    //创始人账号设置为TRUE
    $manager=TRUE;
    //如果cookie中没有风格变量,则风格设置为论坛的默认风格
    $skin = $skinco ? $skinco : $db_defaultstyle;
    //转页面显示语言文件中refresh_limit对应键值的提示信息
    Showmsg("refresh_limit");
  }
}


//引用 在 PHP 中引用意味着用不同的名字访问同一个变量内容。这并不像 C 的指针,替代的是,引用是符号
//表别名。注意在 PHP 中,变量名和变量内容是不一样的,因此同样的内容可以有不同的名字。PHP 的引用允
//许用两个变量来指向同一个内容。意思是,当这样做时: $a =& $b;这意味着 $a 和 $b 指向了
//同一个变量。注: $a 和 $b 在这里是完全相同的,这并不是 $a 指向了 $b 或者相反,而是 $a 和 $b 指向
//了同一个地方。注: 如果具有引用的数组被拷贝,其值不会解除引用。对于数组传值给函数也是如此。

$H_url =& $db_wwwurl;
$B_url =& $db_bbsurl;

//包含data/sql_config.php文件,如果其中有重复包含,略过
require_once(D_P.'data/sql_config.php');

//如果论坛关闭
if ($db_bbsifopen==0){
  //从cookie中取得AdminUser(管理人员)变量值,并对其进行解码操作后,以"\t"分割存入数组$CK中
  $CK = explode("\t",StrCode(GetCookie('AdminUser'),'DECODE'));
  //如果上次访问时间$CK[0]与现在相差超过1800s即30分钟
  //或者账号$CK[1]不是创始人账号$manager
  //或者不符合安全检测

  if ($timestamp-$CK[0]>1800 || $CK[1]!=$manager || !SafeCheck($CK,PwDCode($manager_pwd))){
    //取得风格值
    $skin = $skinco ? $skinco : $db_defaultstyle;
    //设置用户组为空
    $groupid = '';
    //转页面显示$db_whybbsclose信息即论坛关闭的原因
    Showmsg($db_whybbsclose);
  }
}


//gmdate -- 格式化一个 GMT/UTC 日期/时间
//date -- 格式化一个本地时间/日期,返回将整数 timestamp 按照给定的格式字串而产生的
//字符串。如果没有给出时间戳则使用本地当前时间。换句话说,timestamp 是可选的,默认值为 time()。注:
//有效的时间戳典型范围是格林威治时间 1901 年 12 月 13 日 20:45:54 到 2038 年 1 月 19 日
//03:14:07。(此范围符合 32 位有符号整数的最小值和最大值)。不过在 PHP 5.1 之前此范围在某些系统
//(如 Windows)中限制为从 1970 年 1 月 1 日到 2038 年 1 月 19 日。
//格式字串可以识别以下 format 参数的字符串 format 字符 说明 返回值例子
//日 --- ---
//d 月份中的第几天,有前导零的 2 位数字 01 到 31
//D 星期中的第几天,文本表示,3 个字母 Mon 到 Sun
//j 月份中的第几天,没有前导零 1 到 31
//l(“L”的小写字母) 星期几,完整的文本格式 Sunday 到 Saturday
//N ISO-8601 格式数字表示的星期中的第几天(PHP 5.1.0 新加) 1(表示星期一)到 7(表示星期天)
//S 每月天数后面的英文后缀,2 个字符 st,nd,rd 或者 th。可以和 j 一起用
//w 星期中的第几天,数字表示 0(表示星期天)到 6(表示星期六)
//z 年份中的第几天 0 到 366
//星期 --- ---
//W ISO-8601 格式年份中的第几周,每周从星期一开始(PHP 4.1.0 新加的)
//月 --- ---
//F 月份,完整的文本格式,例如 January 或者 March January 到 December
//m 数字表示的月份,有前导零 01 到 12
//M 三个字母缩写表示的月份 Jan 到 Dec
//n 数字表示的月份,没有前导零 1 到 12
//t 给定月份所应有的天数 28 到 31
//年 --- ---
//L 是否为闰年 如果是闰年为 1,否则为 0
//o ISO-8601 格式年份数字。这和 Y 的值相同,只除了如果 ISO 的星期数(W)属于前一年或下一年,则用
//那一年。(PHP 5.1.0 新加) Examples: 1999 or 2003
//Y 4 位数字完整表示的年份 例如:1999 或 2003
//y 2 位数字表示的年份 例如:99 或 03
//时间 --- ---
//a 小写的上午和下午值 am 或 pm
//A 大写的上午和下午值 AM 或 PM
//B Swatch Internet 标准时 000 到 999
//g 小时,12 小时格式,没有前导零 1 到 12
//G 小时,24 小时格式,没有前导零 0 到 23
//h 小时,12 小时格式,有前导零 01 到 12
//H 小时,24 小时格式,有前导零 00 到 23
//i 有前导零的分钟数 00 到 59
//s 秒数,有前导零 00 到 59
//时区 --- ---
//e 时区标识(PHP 5.1.0 新加) 例如:UTC,GMT,Atlantic/Azores
//I 是否为夏令时 如果是夏令时为 1,否则为 0
//O 与格林威治时间相差的小时数 例如:+0200
//T 本机所在的时区 例如:EST,MDT(在 Windows 下为完整文本格式,例如“Eastern
//Standard Time”,中文版会显示“中国标准时间”)。
//Z 时差偏移量的秒数。UTC 西边的时区偏移量总是负的,UTC 东边的时区偏移量总是正的。 -43200 到 43200
//完整的日期/时间 --- ---
//c ISO 8601 格式的日期(PHP 5 新加) 2004-02-12T15:19:21+00:00
//r RFC 822 格式的日期 例如:Thu, 21 Dec 2000 16:01:07 +0200
//U 从 Unix 纪元(January 1 1970 00:00:00 GMT)开始至今的秒数 参见 time()
//格式字串中不能被识别的字符将原样显示。Z 格式在使用 gmdate() 时总是返回 0。
//取当前默认时区时间的24小时时间值,赋于$t['hours']

$t = array('hours'=>gmdate('G',$timestamp+$db_timedf*3600));
//floor -- 舍去法取整
//今天的起始时间

$tdtime = (floor($timestamp/3600)-$t['hours'])*3600;
//是否需要更新在线情况标记
$runfc = 'N';
//如果上次访问时间和当前时间的时间差大于论坛设置的在线用户时限
//或者板块$fid不为空且不等于cookie中的上次访问板块id
//或者上次访问板块id的cookie存在且$wind_in(论坛动作)为hm(论坛首页)

if($timestamp-$lastvisit>$db_onlinetime || ($fid && $fid != GetCookie('lastfid')) || (GetCookie('lastfid') && $wind_in=='hm')){
  //设置cookie中上次访问板块id
  Cookie('lastfid',$fid);
  //设置$runfc为Y
  $runfc='Y';
  //包含data/userglobal.php文件,如果其中有重复包含,略过
  require_once(R_P.'require/userglobal.php');
}

//包含require/db_'.$database.'.php文件,如果其中有重复包含,略过(此处为db_MySQL.php)
require_once(R_P.'require/db_'.$database.'.php');
//创建数据库连接
$db = new DB($dbhost, $dbuser, $dbpw, $dbname, $pconnect);
//销毁$dbhost,$dbuser,$dbpw,$dbname,$pconnect,$manager_pwd变量
unset($dbhost,$dbuser,$dbpw,$dbname,$pconnect,$manager_pwd);
//从cookie中取得winduser信息解码用"\t"分割后存入$winduid,$windpwd
list($winduid,$windpwd)=explode("\t",StrCode(GetCookie('winduser'),'DECODE'));
//若用户登陆id存在且密码长度大等于16
if($winduid && strlen($windpwd)>=16){
  //定义$winddb为用户信息数组
  $winddb = User_info();
  //用户id
  $winduid = $winddb['uid'];
  //用户组id
  $groupid = $winddb['groupid'];
  //用户实际威望为数据库中威望值/10取整
  $userrvrc = (int)($winddb['rvrc']/10);
  //用户名
  $windid = $winddb['username'];
  //用户时间显示格式
  $_datefm = $winddb['datefm'];
  //用户时区设置
  $_timedf = $winddb['timedf'];
  //用户皮肤
  $skin = $winddb['style'] ? $winddb['style'] : $db_defaultstyle;
  //用户在线ip
  $winddb['onlineip']=substr($winddb['onlineip'],0,strpos($winddb['onlineip'],'|'));
  //如果用户组id为-1则设置用户组id为用户的会员组id
  $groupid=='-1' && $groupid=$winddb['memberid'];
  //若用户开启签名显示功能 且
  //((用户开始使用签名时间为空 且 签名显示一天需要交易币个数不为空 且当前用户组属于需要购买签名显示的用户组 且 用户拥有的交易币数大于签名显示一天需要交易币个数) 或者
  //(用户开始使用签名时间不为空 且 用户开始使用签名时间不等于今日开始时间))

  if($winddb['showsign'] && (!$winddb['starttime'] && $db_signmoney && strpos($db_signgroup,",$groupid,") !== false && $winddb['currency'] > $db_signmoney || $winddb['starttime'] && $winddb['starttime'] != $tdtime)){
    //包含require/Signfunc.php文件,如果其中有重复包含,略过
    require_once(R_P.'require/Signfunc.php');
    //对用交易币购买签名做相应的操作
    Signfunc($winddb['showsign'],$winddb['starttime'],$winddb['currency']);
  }
}
else{
  //若登陆id不存在
  //设置风格为论坛默认风格

  $skin = $db_defaultstyle;
  //用户组为游客组
  $groupid = 'guest';
  //销毁$winddb
  unset($winddb);
  //设置用户名,用户id,用户时间显示方式,用户时区设置
  $windid=$winduid=$_datefm=$_timedf='';
}
//取最优先的风格
$_GET['skinco'] && $skinco=$_GET['skinco'];
$_POST['skinco'] && $skinco=$_POST['skinco'];
//如果风格存在,且D_P."data/style/$skinco.php"文件存在,风格变量中不包含".."
if($skinco && file_exists(D_P."data/style/$skinco.php") && strpos($skinco,'..')===false){
  //设置风格变量
  $skin=$skinco;
  //设置风格cookie
  Cookie('skinco',$skinco);
}
//对处在ip封禁列表中的ip禁止登陆
Ipban();
//设置上次访问cookie,包括此次在线时间、当前时间、请求页面
Cookie('lastvisit',$c_oltime."\t".$timestamp."\t".$REQUEST_URI);

//如果当前用户组不是游客
if($groupid!='guest'){
  //且相应组别配置文件存在
  if(file_exists(D_P."data/groupdb/group_$groupid.php")){
    //包含相应组别配置文件,如有重复,略过
    require_once(D_P."data/groupdb/group_$groupid.php");
  }else{
    //否则载入默认组别配置文件
    require_once(D_P."data/groupdb/group_1.php");
  }
}
else{
  //载入游客组配置文件
  require_once(D_P."data/groupdb/group_2.php");
}

//HTTP_REFERER 链接到当前页面的前一页面的 URL 地址。不是所有的用户代理(浏览器)都会设置这个变量,而且有的还可以手工修改 HTTP_REFERER。因此,这个变量不总是真实正确的。
//HTTP_HOST 当前请求的 Host: 头信息的内容。
//如果允许用户使用宣传代码且用户名非空且传入的参数$u为数字
//或者传入的参数$a存在且长度不超过16且HTTP_REFERER中不含HTTP_HOST

if($db_ads && !$windid && (is_numeric($u) || ($a && strlen($a)<16)) && strpos($_SERVER['HTTP_REFERER'],$_SERVER['HTTP_HOST'])===false){
  //设置用户宣传cookie userads
  Cookie('userads',"$u\t$a");
}

//如果SCR常量没有定义
if(!defined('SCR')){
  //定义其为other
  define('SCR','other');
}
//标记页面类型
$SCR = SCR;
$header_ad=$footer_ad='';
//如果页面类型不是帖子页面
if(SCR != 'read'){
  //取得广告信息数组
  $advertdb = AdvertInit(SCR,$fid);
  //is_array -- 检测变量是否是数组
  if(is_array($advertdb['header'])){
    //array_rand -- 从数组中随机取出一个或多个单元
    //取得页首广告

    $header_ad = $advertdb['header'][array_rand($advertdb['header'])]['code'];
  }
  if(is_array($advertdb['footer'])){
    //取得页尾广告
    $footer_ad = $advertdb['footer'][array_rand($advertdb['footer'])]['code'];
  }
}


//返回页首导航 类似 霏凡论坛 -> 口- 超级灌水
function headguide($guidename=array(),$guide=''){
  global $fid,$jinhua;
  if(is_array($guidename)){
    foreach($guidename as $key=>$value){
        if($key){
          $headguide.=$value ? " -> <a href='$value'>$key</a>" : " -> $key";
        }
    }
  }
else{
    $headguide.=" -> ".$guidename;
  }
  //返回页首导航字符串
  return $headguide;
}

//页面跳转
function refreshto($URL,$content,$statime=1){
  global $db_ifjump;
  //替换url中的特殊字符=
  $URL=str_replace('=','=',$URL);
  //如果开启自动跳转且多少秒后抓向$statime大于0
  if($db_ifjump && $statime>0){
    //清空缓冲
    ob_end_clean();
    //是否允许GZIP 压缩输出,允许使用ob_gzhandler压缩技术,否则只启动缓冲
    global $tplpath,$fid,$imgpath,$db_obstart,$db_bbsname,$skin,$B_url;
    $index_name =& $db_bbsname;
    $index_url =& $B_url;
    $db_obstart==1 ? ob_start('ob_gzhandler') : ob_start();
    //确定引用风格
    if(file_exists(D_P."data/style/$skin.php") && strpos($skin,'..')===false){
        include_once(D_P."data/style/$skin.php");
    }else{
        include_once(D_P."data/style/wind.php");
    }
    //解出全局变量数组,如果有冲突不覆盖
    @extract($GLOBALS, EXTR_SKIP);
    //包含对应风格目录下的lang_refreshto.php语言文件,如果其中有重复包含,略过
    require_once GetLang('refreshto');
    $lang[$content] && $content=$lang[$content];
    //包含对应风格目录下的refreshto.htm文件
    @require PrintEot('refreshto');
    //退出
    exit;
  } else{
    ObHeader($URL);
  }
}


//页面直接转向
function ObHeader($URL){
  global $db_obstart,$db_bbsurl,$db_htmifopen;
  //strtolower-小写处理字符串
  //如果开启静态目录部署功能且url中不是http开头

  if($db_htmifopen && strtolower(substr($URL,0,4))!='http'){
    $URL="$db_bbsurl/$URL";
  }
  //页面直接转向
  if($db_obstart){
    //header -- 发送一个原始 HTTP 标头
    header("Location: $URL");exit;
  }else{
    ob_start();
    echo "<meta http-equiv='refresh' content='0;url=$URL'>";
    exit;
  }
}


//转向显示提示信息的页面显示提示信息
function Showmsg($msg_info,$dejump=0){
  //extract -- 从数组中将变量导入到当前的符号表
  //extract() 检查每个键名看是否可以作为一个合法的变量名,同时也检查和符号表中已有的变量名的冲突。对待非法/数字和冲突的键名的方法将根据 extract_type 参数决定。可以是以下值之一:
  //EXTR_OVERWRITE(如果有冲突,覆盖已有的变量)
  //EXTR_SKIP(如果有冲突,不覆盖已有的变量)
  //EXTR_PREFIX_SAME(如果有冲突,在变量名前加上前缀 prefix)
  //EXTR_PREFIX_ALL(给所有变量名加上前缀 prefix)
  //EXTR_PREFIX_INVALID(仅在非法/数字的变量名前加上前缀 prefix)
  //EXTR_IF_EXISTS(仅在当前符号表中已有同名变量时,覆盖它们的值。其它的都不处理。可以用在已经定义了一组合法的变量,然后要从一个数组例如 $_REQUEST 中提取值覆盖这些变量的场合。)
  //EXTR_PREFIX_IF_EXISTS(仅在当前符号表中已有同名变量时,建立附加了前缀的变量名,其它的都不处理)
  //EXTR_REFS(将变量作为引用提取。这有力地表明了导入的变量仍然引用了 var_array 参数的值。可以单独使用这个标志或者在 extract_type 中用 OR 与其它任何标志结合使用)
  //如果没有指定 extract_type,则被假定为 EXTR_OVERWRITE。
  //extract() 返回成功导入到符号表中的变量数目。
  //$GLOBALS由所有已定义全局变量组成的数组。变量名就是该数组的索引。
  //将全局变量导入当前符号表

  @extract($GLOBALS, EXTR_SKIP);
  //在函数中声明了全局变量,任何变量的所有引用变量都会指向到全局变量。包括修改
  //声明全局变量,风格图片文件路径$stylepath,表格宽度$tablewidth,小表格宽度$mtablewidth,风格模板路径$tplpath,$runfc

  global $stylepath,$tablewidth,$mtablewidth,$tplpath,$runfc;
  $runfc='';
  //检查SIMPLE常量是否定义,如果定义过
  if(defined('SIMPLE')){
    //<base>是一个链接基准标记,用以改变页面中所有链接标记的参数默认值。
    echo "<base href=\"$db_bbsurl/\">";
  }
  //包含require/header.php文件,如果其中有重复包含,略过
  require_once(R_P.'require/header.php');
  //包含对应风格目录下的lang_msg.php语言文件,如果其中有重复包含,略过
  require_once GetLang('msg');
  //如果相应的语言文件中对应键值存在,则设置$msg_info为对应值
  $lang[$msg_info] && $msg_info=$lang[$msg_info];
  //包含对应风格目录下的showmsg.htm文件
  require_once PrintEot('showmsg');
  exit;
}

//返回参数指定的文件相应风格语言文件路径
function GetLang($lang,$EXT="php"){
  //声明全局变量风格模板路径
  global $tplpath;
  //路径为……
  $path=R_P."template/$tplpath/lang_$lang.$EXT";
  //如果该风格下模板指定的文件不存在,则路径为默认的wind风格下的对应文件
  !file_exists($path) && $path=R_P."template/wind/lang_$lang.$EXT";
  return $path;
}

//返回参数指定的文件相应风格模板路径
function PrintEot($template,$EXT="htm"){
  //Copyright (c) 2003-06 PHPWind
  //声明全局变量风格模板路径

  global $tplpath;
  //如果参数为空,则设置参数值为N
  if(!$template) $template=N;
  //路径为……
  $path=R_P."template/$tplpath/$template.$EXT";
  //如果该风格下模板指定的文件不存在,则路径为默认的wind风格下的对应文件
  !file_exists($path) && $path=R_P."template/wind/$template.$EXT";
  return $path;
}

//设置相关cookie
function Cookie($ck_Var,$ck_Value,$ck_Time = 'F'){
  //声明全局变量COOKIE有效目录$db_ckpath,COOKIE有效域名$db_ckdomain,$timestamp
  global $db_ckpath,$db_ckdomain,$timestamp;
  //如果设置有效的cookie时间参数为F则,cookie有效时间设置为31536000s即1年
  //否则如果传入的cookie参数值为空并且有效时间参数为0则设置cookie有效时间为31536000s即1年前,也就是无效
  //如果都不是则设置时间为传入的cookie有效时间参数值

  $ck_Time = $ck_Time == 'F' ? $timestamp + 31536000 : ($ck_Value == '' && $ck_Time == 0 ? $timestamp - 31536000 : $ck_Time);
  //SERVER_PORT-服务器所使用的端口。默认为“80”。如果使用 SSL 安全连接,则这个值为用户设置的 HTTP 端口。
  //判断是否使用安全连接

  $S = $_SERVER['SERVER_PORT'] == '443' ? 1:0;
  //如果COOKIE有效目录为空则设置其为根目录
  !$db_ckpath && $db_ckpath = '/';
  //setcookie -- 发送一个 cookie 信息
  //参数说明:Cookie 的名字,Cookie 的值,Cookie 过期的时间(这是个 Unix 时间戳,即从 Unix 纪元开始的秒数),Cookie 在服务器端的有效路径,该 cookie 有效的域名,指明 cookie 是否仅通过安全的 HTTPS 连接传送(当设成 TRUE 时,cookie 仅在安全的连接中被设置,默认值为 FALSE)

  setCookie($ck_Var,$ck_Value,$ck_Time,$db_ckpath,$db_ckdomain,$S);
}

//取cookie中的某变量值
function GetCookie($Var){
  //_COOKIE 通过 HTTP cookies 传递的变量组成的数组。是自动全局变量。
  return $_COOKIE[$Var];
}

//对处在ip封禁列表中的ip禁止登陆
function Ipban(){
  //声明全局变量IP封禁列表$db_ipban,客户端ip$onlineip,图片目录$imgpath,风格图片文件路径$stylepath
  global $db_ipban,$onlineip,$imgpath,$stylepath;
  //如果ip封禁列表不为空
  if($db_ipban){
    //以","分割
    $baniparray=explode(",",$db_ipban);
    foreach($baniparray as $banip){
        //ip为空继续下个循环
        if(!$banip)continue;
        //去掉变量中的空格
        $banip=trim($banip);
        //如果客户端ip在被封禁列表中
        if(strpos(','.$onlineip.'.',','.$banip.'.')!==false){
          //转页面显示语言文件中ip_ban对应键值的提示信息
          Showmsg('ip_ban');
        }
    }
  }
}


//删除文件
function P_unlink($filename){
  //判断是否$filename是否包含..,包含则退出显示Forbidden
  strpos($filename,'..')!==false && exit('Forbidden');
  //unlink -- 删除文件
  @unlink($filename);
}

//读取文件内容
function readover($filename,$method="rb"){
  //由于strpos函数返回是整数值,所以使用!==(不全等)符号判断是否$filename是否包含..,包含则退出显示Forbidden
  strpos($filename,'..')!==false && exit('Forbidden');
  //fopen -- 打开文件或者 URL,
  //mode 说明
  //'r' 只读方式打开,将文件指针指向文件头。
  //'r+' 读写方式打开,将文件指针指向文件头。
  //'w' 写入方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。
  //'w+' 读写方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。
  //'a' 写入方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。
  //'a+' 读写方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。
  //'x' 创建并以写入方式打开,将文件指针指向文件头。如果文件已存在,则 fopen() 调用失败并返回 FALSE,
  //并生成一条 E_WARNING 级别的错误信息。如果文件不存在则尝试创建之。这和给 底层的 open(2) 系统调
  //用指定 O_EXCL|O_CREAT 标记是等价的。此选项被 PHP 4.3.2 以及以后的版本所支持,仅能用于本地文
  //件。
  //'x+' 创建并以读写方式打开,将文件指针指向文件头。如果文件已存在,则 fopen() 调用失败并返回
  //FALSE,并生成一条 E_WARNING 级别的错误信息。如果文件不存在则尝试创建之。这和给 底层的 open
  //(2) 系统调用指定 O_EXCL|O_CREAT 标记是等价的。此选项被 PHP 4.3.2 以及以后的版本所支持,仅能用
  //于本地文件。
  //@为错误运算符。当将其放置在一个 PHP 表达式之前,该表达式可能产生的任何错误信息都被忽略掉。
  //以读方式打开文件,并将返回的资源(resource)保存在$handle变量中

  if($handle=@fopen($filename,$method)){
    //flock -- 轻便的咨询文件锁定
    //flock() 操作的 handle 必须是一个已经打开的文件指针。operation 可以是以下值之一:LOCK_SH取得共享锁定、LOCK_EX取得独占锁定、LOCK_UN释放锁定、LOCK_NB不希望 flock() 在锁定时堵塞

    flock($handle,LOCK_SH);
    //filesize返回文件字节数
    //fread() 从文件指针 handle 读取最多 length 个字节(这里为全部),返回类型为string

    $filedata=@fread($handle,filesize($filename));
    //fclose -- 关闭一个已打开的文件指针
    fclose($handle);
  }
  //返回文件内容
  return $filedata;
}

//写入文件
function writeover($filename,$data,$method="rb+",$iflock=1,$check=1,$chmod=1){
  //Copyright (c) 2003-06 PHPWind
  //判断是否$filename是否包含..,包含则退出显示Forbidden

  $check && strpos($filename,'..')!==false && exit('Forbidden');
  //touch -- 设定文件的访问和修改时间
  touch($filename);
  //以读写方式打开文件,并将返回的资源(resource)保存在$handle变量中
  $handle=fopen($filename,$method);
  if($iflock){
    flock($handle,LOCK_EX);
  }
  //fwrite -- 写入文件(可安全用于二进制文件)
  fwrite($handle,$data);
  //ftruncate -- 将文件截断到给定的长度
  if($method=="rb+") ftruncate($handle,strlen($data));
  fclose($handle);
  //chmod -- 改变文件模式
  //mode 参数包含三个八进制数按顺序分别指定了所有者、所有者所在的组以及所有人的访问限制。每一部分都可以通过加入所需的权限来计算出所要的权限。数字 1 表示使文件可执行,数字 2 表示使文件可写,数字 4 表示使文件可读。

  $chmod && @chmod($filename,0777);
}

//打开文件,返回文件数组
function openfile($filename){
  //读取文件内容
  $filedata=readover($filename);
  //做相应替换
  $filedata=str_replace("\n","\n<:wind:>",$filedata);
  //以"<:wind:>"分割,相当于每行一个数组元素
  $filedb=explode("<:wind:>",$filedata);
  //count -- 计算数组中的单元数目或对象中的属性个数,返回 var 中的单元数目,通常是一个 array,任何其它
  //类型都只有一个单元。对于对象,如果安装了 SPL,可以通过实现 Countable 接口来调用 count()。该接口
  //只有一个方法 count(),此方法返回 count() 函数的返回值。如果 var 不是数组类型或者实现了Countable
  //接口的对象,将返回 1,有一个例外,如果 var 是 NULL 则结果是 0。注: 可选的 mode 参数自 PHP
  //4.2.0 起可用。如果可选的 mode 参数设为 COUNT_RECURSIVE(或 1),count() 将递归地对数组计
  //数。对计算多维数组的所有单元尤其有用。mode 的默认值是 0。count() 识别不了无限递归。count() 对没
  //有初始化的变量返回 0,但对于空的数组也会返回 0。用 isset() 来测试变量是否已经初始化。

  $count=count($filedb);
  //如果数组最后一个元素为空或者为"\r",销毁此元素
  if($filedb[$count-1]==''||$filedb[$count-1]=="\r"){unset($filedb[$count-1]);}
  //empty -- 检查一个变量是否为空,如果 var 是非空或非零的值,则 empty() 返回 FALSE。换句话说,
  //""、0、"0"、NULL、FALSE、array()、var $var; 以及没有任何属性的对象都将被认为是空的,如果 var
  //为空,则返回 TRUE。除了当变量没有置值时不产生警告之外,empty() 是 (boolean) var 的反义词。

  if(empty($filedb)){$filedb[0]="";}
  //返回文件数组
  return $filedb;
}

//更新在线信息
function Update_ol(){
  global $runfc;
  if($runfc == 'Y'){
    //声明全局变量在在线文件中的偏移量$ol_offset,用户id$winduid,是否开启IP统计$db_ipstates,是否修改$isModify
    global $ol_offset,$winduid,$db_ipstates,$isModify;
    //如果用户id存在
    if($winduid != ''){
        //更新在线会员文件
        list($alt_offset,$isModify) = addonlinefile($ol_offset,$winduid);
    }else{
        //更新在线游客文件
        list($alt_offset,$isModify) = addguestfile($ol_offset);
    }
    //如果返回的文件中偏移量和当前cookie中的不同,更新该cookie
    if($alt_offset!=$ol_offset)Cookie('ol_offset',$alt_offset,0);
    //重置是否更新在线信息标记为空
    $runfc='';
    //如果开启ip统计 且
    //(cookie中ipstate信息为空且修改标记为是 或
    //cookie中ipstate信息存在但其小于今日开始时间)

    if($db_ipstates && ((!GetCookie('ipstate') && $isModify===1) || (GetCookie('ipstate') && GetCookie('ipstate')<$GLOBALS['tdtime']))){
        //包含require/ipstates.php,若有重复,略过
        require_once(R_P.'require/ipstates.php');
    }
  }
}


//页脚处理显示函数
function footer(){
  //声明全局变量数据库访问类$db,是否开启GZIP 压缩输出$db_obstart,是否在页脚显示程序运行时间$db_footertime,是否开启静态目录部署功能$db_htmifopen,当前时间秒数$P_S_T,头部和尾部表格宽度$mtablewidth,联系我们URL$db_CEOconnect,PHPWind版本$wind_version,图片目录$imgpath,此风格在image目录下的文件夹名称$stylepath,页脚广告$footer_ad,论坛联盟广告$db_union,当前时间$timestamp
  global $db,$db_obstart,$db_footertime,$db_htmifopen,$P_S_T,$mtablewidth,$db_ceoconnect,$wind_version,$imgpath,$stylepath,$footer_ad,$db_union,$timestamp;
  //更新在线信息
  Update_ol();
  //如果访问类不为空
  if($db){
    //记录查询次数
    $qn=$db->query_num;
  }
  //显示是否开始gzip压缩
  $ft_gzip=($db_obstart==1 ? "Gzip enabled" : "Gzip disabled").$db_union[3];
  //如果设置了允许在页脚显示程序运行时间
  if ($db_footertime == 1){
    $t_array = explode(' ',microtime());
    //number_format 按照千分制划分数字,string number_format ( float number [, int decimals [,
    //string dec_point, string thousands_sep]] )它接受一个、两个或四个参数,不接受三个;给
    //定一个将没有十进制 . 点号,只有千分制 , 逗号;两个,具有十进制的 . 点号和千进制的 , 句号;四个,十进
    //制、千进制全部用 , 都好划分;注:仅千进制的第一个字符被使用;例如,你使用foo作为千进制的区分,那
    //么1000就成为1f000;

    $totaltime = number_format(($t_array[0]+$t_array[1]-$P_S_T),6);
    $wind_spend = "Total $totaltime(s) query $qn,";
  }
  //按格式取当前时间
  $ft_time=get_date($timestamp,'m-d H:i');
  //包含对应风格目录下的footer.htm文件
  include PrintEot('footer');
  //ob_get_contents 返回输出缓存的内容,如输出缓存未被激活,将返回缓存内容或Flase;
  $output = str_replace(array('<!--<!---->','<!---->'),array('',''),ob_get_contents());
  //如果开启静态目录功能
  if($db_htmifopen){
    //preg_replace -- 执行正则表达式的搜索和替换,mixed preg_replace ( mixed pattern, mixed
    //replacement, mixed subject [, int limit] )在 subject 中搜索 pattern 模式的匹配项并替换为
    //replacement。如果指定了 limit,则仅替换 limit 个匹配,如果省略 limit 或者其值为 -1,则所有的匹配项
    //都会被替换。replacement 可以包含 \\n 形式或(自 PHP 4.0.4 起)$n 形式的逆向引用,首选使用后者。
    //每个此种引用将被替换为与第 n 个被捕获的括号内的子模式所匹配的文本。n 可以从 0 到 99,其中 \\0 或
    //$0 指的是被整个模式所匹配的文本。对左圆括号从左到右计数(从 1 开始)以取得子模式的数目。对替换模式
    //在一个逆向引用后面紧接着一个数字时(即:紧接在一个匹配的模式后面的数字),不能使用熟悉的 \\1 符号
    //来表示逆向引用。举例说 \\11,将会使 preg_replace() 搞不清楚是想要一个 \\1 的逆向引用后面跟着一个
    //数字 1 还是一个 \\11 的逆向引用。本例中的解决方法是使用 \${1}1。这会形成一个隔离的 $1 逆向引用,
    //而使另一个 1 只是单纯的文字。

    $output = preg_replace(
        "/\<a(\s*[^\>]+\s*)href\=([\"|\']?)([^\"\'>\s]+\.php\?[^\"\'>\s]+)([\"|\']?)/ies",
        "Htm_cv('\\3','<a\\1href=\"')",
        $output
    );
  }
  ob_end_clean();
  $db_obstart == 1 ? ob_start('ob_gzhandler') : ob_start();
  echo $output;
  //flush -- 刷新输出缓冲,刷新PHP程序的缓冲,而不论PHP执行在何种情况下(CGI ,web服务器等等)。该
  //函数将当前为止程序的所有输出发送到用户的浏览器。flush() 函数不会对服务器或客户端浏览器的缓存模式产
  //生影响。因此,必须同时使用 ob_flush() 和flush() 函数来刷新输出缓冲。个别web服务器程序,特别是
  //Win32下的web服务器程序,在发送结果到浏览器之前,仍然会缓存脚本的输出,直到程序结束为止。有些
  //Apache的模块,比如mod_gzip,可能自己进行输出缓存,这将导致flush()函数产生的结果不会立即被发送
  //到客户端浏览器。甚至浏览器也会在显示之前,缓存接收到的内容。例如 Netscape 浏览器会在接受到换行
  //或 HTML 标记的开头之前缓存内容,并且在接受到 </table> 标记之前,不会显示出整个表格。一些版本的
  //Microsoft Internet Explorer 只有当接受到的256个字节以后才开始显示该页面,所以必须发送一些额外的
  //空格来让这些浏览器显示页面内容。

  flush;
  exit;
}

//返回静态目录功能开始时页面的访问路径
function Htm_cv($url,$TAG){
  //声明全局变量静态目录$db_dir,静态目录扩展名设置$db_ext
  global $db_dir,$db_ext;
  //ereg -- 正则表达式匹配,注: 使用 Perl 兼容正则表达式语法的 preg_match() 函数通常是比 ereg() 更快
  //的替代方案。以区分大小写的方式在 string 中寻找与给定的正则表达式 pattern 所匹配的子串。如果找到与
  //pattern 中圆括号内的子模式相匹配的子串并且函数调用给出了第三个参数 regs,则匹配项将被存入 regs
  //数组中。$regs[1] 包含第一个左圆括号开始的子串,$regs[2] 包含第二个子串,以此类推。$regs[0] 包含
  //整个匹配的字符串。注: 直到 PHP 4.1.0 为止,$regs 将被填充为正好十个单元,即使实际匹配的子串少于十
  //个。这并不影响 ereg() 匹配更多子串的能力。如果没有找到匹配,则 $regs 不会被 ereg() 更改。 如果在
  //string 中找到 pattern 模式的匹配则返回 TRUE,如果没有找到匹配或出错则返回 FALSE。

  if(ereg("^http|FTP|telnet|mms|rtsp|admin.php|RSS.php",$url)===false){
    if(strpos($url,'#')!==false){
        $add = substr($url,strpos($url,'#'));
    }
    $url = str_replace(
        array('.php?','=','&',$add),
        array($db_dir,'-','-',''),
        $url
    ).$db_ext.$add;
  }
  return $tag.$url.'"';
}

//取得用户信息数组
function User_info(){
  //声明全局变量数据库访问类$db,当前时间$timestamp,在线用户时限$db_onlinetime,用户id$winduid,用户密码$windpwd,是否记录会员在线时间并在版块内显示$db_ifonlinetime,用户此次在线时间$c_oltime,客户端ip$onlineip,是否开启IP验证功能$db_ipcheck,今日开始时间$tdtime;
  global $db,$timestamp,$db_onlinetime,$winduid,$windpwd,$db_ifonlinetime,$c_oltime,$onlineip,$db_ipcheck,$tdtime;
  $ct='';
  //取得数据表中的相应记录
  //相关字段名解释如下:
  //用户id-m.uid,
  //用户名-m.username,
  //用户密码-m.password,
  //用户邮箱-m.email,
  //用户qq号-oicq,
  //用户系统组id-m.groupid,
  //用户会员组id-m.memberid,
  //用户注册时间-m.regdate,
  //用户时区设置-m.timedf,
  //用户风格设置-m.style,
  //用户时间显示格式设置-m.datefm,
  //主题列表每页个数-m.t_num,
  //文章列表每页个数-m.p_num,
  //是否已激活-m.yz,
  //是否有新的短消息-m.newpm,
  //是否开启签名展示功能-m.showsign,
  //用户交易帐号信息-m.payemail
  //用户发帖数-md.postnum,
  //用户威望数-md.rvrc,
  //用户金钱数-md.money,
  //用户自定义积分数-md.credit,
  //用户交易币数-md.currency,
  //用户上次访问时间-md.lastvisit,
  //用户这次访问时间-md.thisvisit,
  //用户在线时间-md.onlinetime,
  //用户上次回复时间-md.lastpost,
  //用户今日发帖数-md.todaypost,
  //用户在线ip情况-md.onlineip,
  //用户上传时间-md.uploadtime,
  //用户上传数-md.uploadnum,
  //使用所见即所得编辑器还是windcode编辑器-md.editor,
  //签名使用起始时间-md.starttime

  $detail =$db->get_one("SELECT m.uid,m.username,m.password,m.email,oicq,m.groupid,m.memberid,m.regdate,m.timedf,m.style,m.datefm,m.t_num,m.p_num,m.yz,m.newpm,m.showsign,m.payemail,md.postnum,md.rvrc,md.money,md.credit,md.currency,md.lastvisit,md.thisvisit,md.onlinetime,md.lastpost,md.todaypost,md.onlineip,md.uploadtime,md.uploadnum,md.editor,md.starttime FROM pcr_members m LEFT JOIN pcr_memberdata md USING(uid) WHERE m.uid='$winduid'");
  //如果用户登陆ip字段中不存在当前客户端ip信息
  if(strpos($detail['onlineip'],$onlineip)===false){
    //以"."分割客户端ip
    $iparray=explode(".",$onlineip);
    //如果用户登陆ip字段中不存在当前客户端ip的前2节则退出标记设为Y
    if(strpos($detail['onlineip'],$iparray[0].'.'.$iparray[1])===false) $loginout='Y';
  }
  //如果查询不到该会员信息即$detail为空
  //或者用户密码在编码后和cookie中保存的值不相同
  //或者退出标记为Y且论坛开启了IP验证功能

  if(!$detail || PwdCode($detail['password']) != $windpwd || ($loginout=='Y' && $db_ipcheck==1)){
    //销毁$detail
    unset($detail);
    //设置当前用户组为游客
    $GLOBALS['groupid']='guest';
    //包含require/checkpass.php文件,如果其中有重复包含,略过
    require_once(R_P.'require/checkpass.php');
    //退出并清空cookies
    Loginout();
    //转页面显示语言文件中ip_change对应键值的提示信息
    Showmsg('ip_change');
  }else{
    //销毁用户信息中的密码变量
    unset($detail['password']);
    //如果当前时间和数据库中用户这次访问时间差大于在线用户时限
    if($timestamp-$detail['thisvisit']>$db_onlinetime){
        //非隐身
        if(!GetCookie('hideid')){
          $ct="lastvisit=thisvisit,thisvisit='$timestamp'";
          //更新数组中上次访问时间变量为这次访问时间
          $detail['lastvisit'] = $detail['thisvisit'];
          //更新数组中这次访问时间变量为当前时间
          $detail['thisvisit'] = $timestamp;
        }
        //如果设置了记录会员在线时间并在版块内显示且ct变量不为空,且用户此次在线时间大于0
        if($db_ifonlinetime == 1 && $ct && $c_oltime > 0){
          //如果用户此次在线时间大于在线用户时限的1.2倍
          if($c_oltime > $db_onlinetime*1.2){
            //更新用户此次在线时间为在线用户时限
            $c_oltime = $db_onlinetime;
          }
          $ct .= ",onlinetime=onlinetime+'$c_oltime'";
          $c_oltime = 0;
        }
        //若ct变量不为空,则更新数据库中的上次访问时间、这次访问时间、在线时间
        $ct && $db->update("UPDATE pcr_memberdata SET $ct WHERE uid='$winduid' AND $timestamp-thisvisit>$db_onlinetime");
    }
  }

  //返回用户信息数组
  return $detail;
}

//返回pwd的编码后值
function PwdCode($pwd){
  //连接头信息、$pwd参数(密码)以及论坛安全验证参数,md5加密后返回该值
  return md5($_SERVER["HTTP_USER_AGENT"].$pwd.$GLOBALS['db_hash']);
}

//安全检测,若不符合销毁相应cookie,符合重新设置相应cookie
function SafeCheck($CK,$PwdCode,$var='AdminUser',$expire=1800){
  //声明全局变量$timestamp
  global $timestamp;
  //上次访问时间和当前时间的时间差赋于变量$t
  $t = $timestamp - $CK[0];
  //如果其超过定义的过期时间或者$CK[2]中存储的密码(编码后的密码连接上次访问时间再md5加密后得到的值)并不匹配
  if($t > $expire || $CK[2] != md5($PwdCode.$CK[0])){
    //设置$var状态为无效,即销毁此cookie
    Cookie($var,'',0);
    return false;
  }else{
    //设置上次访问时间为当前时间
    $CK[0] = $timestamp;
    //设置存储的密码信息为编码后的密码连接当前时间再md5加密后得到的值
    $CK[2] = md5($PwdCode.$timestamp);
    //implode 联结数组元素为一个字符串;string implode ( string glue, array pieces )第一个参数为分割符
    //第二个参数为要联结的数组单元;
    //用"\t"连接数组$CK

    $Value = implode("\t",$CK);
    //设置变量$$var为$Value编码后的值
    $$var = StrCode($Value);
    Cookie($var,StrCode($Value));
    return true;
  }
}


//对字符串进行编码或解码处理,并返回编码或解码后得到的值
function StrCode($string,$action='ENCODE'){
  //HTTP_USER_AGENT-当前请求的 User-Agent: 头信息的内容。该字符串表明了访问该页面的用户代理的信息。一个典型的例子是:Mozilla/4.5 [en] (X11; U; Linux 2.2.9 i586)。也可以使用 get_browser() 得到此信息。
  //连接头信息和论坛安全验证参数,md5加密后,从第8位起取18个字符,作为变量key的值

  $key = substr(md5($_SERVER["HTTP_USER_AGENT"].$GLOBALS['db_hash']),8,18);
  //base64_decode -- 对使用 MIME base64 编码的数据进行解码
  //如果选择的操作是编码操作,则string变量不变仍旧为自己本身参数的值,否则为base64解码该参数后得到的值

  $string = $action == 'ENCODE' ? $string : base64_decode($string);
  //记录key的长度,为18
  $len = strlen($key);
  $code = '';
  //对string中的每个字符
  for($i=0; $i<strlen($string); $i++){
    //k变量存储$i % $len余数
    $k = $i % $len;
    //位运算符允许对整型数中指定的位进行置位。如果左右参数都是字符串,则位运算符将操作字符的 ASCII 值。
    //$a ^ $b Xor(按位异或) 将把 $a 和 $b 中不同的位设为 1。
    //把$string[$i]和$key[$k]以ascii值方式进行异或运算后,值累加到code变量中

    $code .= $string[$i] ^ $key[$k];
  }
  //base64_encode -- 使用 MIME base64 对数据进行编码
  //设计此种编码是为了使二进制数据可以通过非纯 8-bit 的传输层传输,例如电子邮件的主体。
  //Base64-encoded 数据要比原始数据多占用 33% 左右的空间。
  //如果选择的操作是加码操作,则code变量不变,否则为base64编码code后得到的值

  $code = $action == 'DECODE' ? $code : base64_encode($code);
  return $code;
}

//返回$content字符串$length长度的值,超过$length显示".."
function substrs($content,$length){
  //取字符集设置
  global $db_charset;
  if($length && strlen($content)>$length){
    if($db_charset!='utf-8'){
        $retstr='';
        for($i = 0; $i < $length - 2; $i++) {
          //ord 返回字符串的第一个字符的二进制值;string chr ( int ascii ) 返回相对应于 ascii 所指定的单个字符。
          $retstr .= ord($content[$i]) > 127 ? $content[$i].$content[++$i] : $content[$i];
        }
        return $retstr.' ..'