- <?php
- /*
- April 18,2012
- discuz二次开发学习
- author:xuqin
- 不能为了完成任务去做一件事,要举一反三,融会贯通的去学习。
- */
- error_reporting(0);
- /*
- * error_reporting(0); //抑制所有的出错信息
- * error_reporting(E_ALL);//显示所有的出错信息
- */
- set_magic_quotes_runtime(0);
- /*
- * set_magic_quotes_runtime(0); //关闭魔法引用
- * set_magic_quotes_runtime(1); //开启魔法引用
- * 什么事魔术引号?
- * 当打开时,所有的 '(单引号),"(双引号),\(反斜线)和 NULL 字符都会被自动加上一个反斜线进行转义。
- * 这和 addslashes() 作用完全相同。
- * 一共有三个魔术引号指令:
- * magic_quotes_gpc 影响到 HTTP 请求数据(GET,POST 和 COOKIE)。不能在运行时改变。在 PHP 中默认值为 on。 参见 get_magic_quotes_gpc()。
- * magic_quotes_runtime 如果打开的话,大部份从外部来源取得数据并返回的函数,包括从数据库和文本文件,所返回的数据都会被反斜线转义。该选项可在运行的时改变,在 PHP 中的默认值为 off。 参见 set_magic_quotes_runtime() 和 get_magic_quotes_runtime()。
- * magic_quotes_sybase 如果打开的话,将会使用单引号对单引号进行转义而非反斜线。此选项会完全覆盖 magic_quotes_gpc。如果同时打开两个选项的话,单引号将会被转义成 ''。而双引号、反斜线 和 NULL 字符将不会进行转义。 如何取得其值参见 ini_get()。
- */
- $mtime = explode(' ', microtime());
- /*
- * microtime()取得Unix 时间戳和微秒数后使用explode()进行切割并存进数组变量$mtime;
- * eg:
- * echo microtime(); //将显示 0.29353300 1164349567 (根据时间的不同会显示不同的数字,但格式是一样的
- * $mtime[0]="0.29353300";
- * $mtime[1]="1164349567";
- */
- $discuz_starttime = $mtime[1] + $mtime[0];//得到脚本开始运行的时间
- define('SYS_DEBUG', FALSE);//关闭debug模式
- /*
- * 编译方式的本质区别 Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
- */
- define('IN_DISCUZ', TRUE);//用于防止非法引用,定义一个常量表示只有DISCUZ才可以使用本脚本
- define('DISCUZ_ROOT', substr(dirname(__FILE__), 0, -7));//定义常量DISCUZ_ROOT的值(即bbs的路径)
- define('MAGIC_QUOTES_GPC', get_magic_quotes_gpc());//将魔术引号开启状态存入常量
- !defined('CURSCRIPT') && define('CURSCRIPT', '');//这个写法很特别哦!!初始化当前运行的脚本名称为空,CURSCRIPT这个常量在很多页面都有,意思是如果没定义CURSCRIPT常量, 则定义CURSCRIPT为空。
- if(PHP_VERSION < '4.1.0') {//php旧版本超全局变量兼容处理,注意是引用赋值"&$" php基础知识哦!去复习下吧!
- $_GET = &$HTTP_GET_VARS;
- $_POST = &$HTTP_POST_VARS;
- $_COOKIE = &$HTTP_COOKIE_VARS;
- $_SERVER = &$HTTP_SERVER_VARS;
- $_ENV = &$HTTP_ENV_VARS;
- $_FILES = &$HTTP_POST_FILES;
- }
- /*
- * 作用:当PHP版本小于4.1.0 的时候使用传址方式获取内置全局变量
- * 示例:
- * 为什么要使用传址而不是传值方式?
- * 要点:在变量前加 & 符号既是传址
- * <?php
- * $a=1;
- * $b=$a;
- * $b++;
- * echo "b=".$b." a=".$a; //显示:b=2 a=1 可以看到 b变了,而a 却还是没变
- *
- * echo "<br>";
- * $c=1;
- * $d=&$c; //注意这行 &符号
- * $d++;
- * echo "d=".$d." c=".$c; //显示: d=2 c=2 可以看到d变了,可是c也变了
- * ?>
- */
- if (isset($_REQUEST['GLOBALS']) OR isset($_FILES['GLOBALS'])) {
- exit('Request tainting attempted.');
- }
- /*
- * 众所周知,当php.ini里面的register_globals=on时,各种变量都被注入代码,例如来自 HTML 表单的请求变量。再加上 PHP 在使用变量之前是无需进行初始化的。那么就有可能导致不安全,假如有人恶意发出这么一个get请求"http://yourdomain /unsafe.php?GLOBALS=",那么就会清除$GLOBALS变量的值而导致不安全。
- *
- * register_globals是php.ini里的一个配置,这个配置影响到php如何接收传递过来的参数,如果你的问题是:为什么我的表单无法传递数据?为什么我的程序无法得到传递过来的变量?等等,那么你需要仔细的阅读以下的内容。
- * register_globals的值可以设置为:On或者Off,我们举一段代码来分别描述它们的不同。
- * 代码:
- * <form name="frmTest" id="frmTest" action="URL">
- * <input type="text" name="user_name" id="user_name">
- * <input type="password" name="user_pass" id="user_pass">
- * <input type="submit" value="login">
- * </form>
- * 当register_globals=Off的时候,下一个程序接收的时候应该用$_GET['user_name'] 和$_GET['user_pass']来接受传递过来的值。(注:当<form>的method属性为post的时候应该用$_POST['user_name']和$_POST['user_pass'])
- * 当register_globals=On的时候,下一个程序可以直接使用$user_name和$user_pass来接受值。
- * 顾名思义,register_globals的意思就是注册为全局变量,所以当On的时候,传递过来的值会被直接的注册为全局变量直接使用,而Off的时候,我们需要到特定的数组里去得到它。
- *
- */
- require_once DISCUZ_ROOT.'./include/global.func.php';//引入全局函数库,很重要
- /*
- * 这里复习下:require;require_once();include();incdlue_once()区别
- * require()
- *include()
- *这两一般放在代码前面,功能除了处理失败情况不一样,其它都是一样,如果包含的文件不存在时,require会停止运行发生致命错误提示。而include则是只显示一警告,代码会继续执行。
- *require_once()
- *include_once()
- *这两一个一般放在流程控制中,除了以上错误处理的区别外,功能都是一样的,跟没有once的区别就是包含的文件已经在前包含过,第二次包含不会再被引入。
- */
- define('IS_ROBOT', getrobot());//定义IS_ROBOT常量,判断浏览此页面的user-agent是否为搜索引擎蜘蛛,getrobot函数见global.func.php
- if(defined('NOROBOT') && IS_ROBOT) {//如果定义了此页不能被机器人找到,但是发现访问者是搜索引擎蜘蛛,则返回禁止信息。
- exit(header("HTTP/1.1 403 Forbidden"));
- }
- foreach(array('_COOKIE', '_POST', '_GET') as $_request) {//过滤提交的变量,提高安全性
- foreach($$_request as $_key => $_value) {
- $_key{0} != '_' && $$_key = daddslashes($_value);//变量名第一个字母不能为下划线,防止有类似伪造的"$_POST"变量产生
- }
- }
- /*
- * addslashes() 函数在指定的预定义字符前添加反斜杠。
- * [单引号 (')双引号 (")反斜杠 (\)NULL]默认情况下,PHP 指令 magic_quotes_gpc 为 on,对所有的 GET、POST 和 COOKIE 数据自动运行 addslashes()。不要对已经被 magic_quotes_gpc 转义过的字符串使用 addslashes(),因为这样会导致双层转义。遇到这种情况时可以使用函数 get_magic_quotes_gpc() 进行检测。
- * stripcslashes() 函数 删除 由 addcslashes() 函数添加的反斜杠。(也可删除服务器开启magic_quotes_gpc后自动添加上的反斜杠)
- */
- if (!MAGIC_QUOTES_GPC && $_FILES) {//转义$_FILES变量,注意这里先要判断MAGIC_QUOTES_GPC常量的状态,如果是关闭的才进行转义,否则会造成多次转义
- $_FILES = daddslashes($_FILES);
- }
- $charset = $dbcharset = $forumfounders = $metakeywords = $extrahead = $seodescription = '';
- //全局变量初始化:初始化文件字符集,数据库字符集,版块创建者,SEO的论坛关键字、描述,头部其他信息等变量
- $plugins = $hooks = $admincp = $jsmenu = $forum = $thread = $language = $actioncode = $modactioncode = $lang = array();
- /*
- * 初始化插件,钩子,后台管理,js弹出菜单,版块,帖子,语言,动作代码,系统提示信息等数组
- * [钩子]实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达前台目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。
- */
- require_once DISCUZ_ROOT.'./config.inc.php';//引入系统配置文件,内含如数据库连接信息、cookie域、UC信息等
- $_DCOOKIE = $_DSESSION = $_DCACHE = $_DPLUGIN = $advlist = array();//初始化cookie,session,缓存,插件,广告列表等数组
- $prelength = strlen($cookiepre);//定义(获取)COOKIE前缀长度$prelength为$cookiepre的长度(见config.inc)
- foreach($_COOKIE as $key => $val) {
- if(substr($key, 0, $prelength) == $cookiepre) {
- $_DCOOKIE[(substr($key, $prelength))] = MAGIC_QUOTES_GPC ? $val : daddslashes($val);
- }
- }
- /*
- * 从浏览器$_COOKIE得到$_DCOOKIE数组:如果cookie的前缀与系统配置的cookie前缀相同,则填入各cookie的值
- 这里$_DCOOKIE各变量的名称是不包含前缀的(例如$_COOKIE['tmp_cook1']变成了$_DCOOKIE['cook1']),同时对值进行转义
- */
- unset($prelength, $_request, $_key, $_value);
- /*
- * 上面对COOKIE遍历完成后,便立即销毁$prelength等相关变量。
- * 清空一些无用变量,如临时的$_POST、$_GET等(因为已经在本文件前面处理中得到了很多的单独变量)
- */
- $inajax = !emptyempty($inajax);//先定义一个备用的变量吧,与ajax有关吗?
- $timestamp = time();//获得一个当前的时间戳变量,如:1334714617,与前面到microtime不同,time()只会得到毫秒数
- if($attackevasive && CURSCRIPT != 'seccode') {
- require_once DISCUZ_ROOT.'./include/security.inc.php';//防御文件,根据$attackevasive设定的不同级别做相应处理。
- }
- /*
- * 是否启用攻击防御。$attackevasive默认为0,关闭论坛防御。如果配置文件config.inc定义了论坛防御级别,且当前脚本文件名不是seccode(验证码?),则引入相关防御的文件
- */
- require_once DISCUZ_ROOT.'./include/db_'.$database.'.class.php';//引入数据库处理类
- $PHP_SELF = $_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME'];//获得当前执行脚本的文件相对路径
- $BASESCRIPT = basename($PHP_SELF);//返回路径的文件名部分
- $boardurl = htmlspecialchars('http://'.$_SERVER['HTTP_HOST'].preg_replace("/\/+(api|archiver|wap)?\/*$/i", '', substr($PHP_SELF, 0, strrpos($PHP_SELF, '/'))).'/');
- /*
- * 取当前url 返回bbs的url(htmlspecialchars将特殊字符”<>“等转换为”< $gt“等)仔细看下substr、strpos那段,看这样是什么效果
- * strrpos() 函数查找字符串在另一个字符串中最后一次出现的位置
- * preg_replace() 执行一个正则表达式的搜索和替换
- */
- if(getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) {
- $onlineip = getenv('HTTP_CLIENT_IP');
- } elseif(getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) {
- $onlineip = getenv('HTTP_X_FORWARDED_FOR');
- } elseif(getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) {
- $onlineip = getenv('REMOTE_ADDR');
- } elseif(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) {
- $onlineip = $_SERVER['REMOTE_ADDR'];
- }
- /*
- * 获得用户到ip地址,存入$onlineip变量
- * 注意PHP三个获取ip的系统变量。
- * HTTP_CLIENT_IP 是tcp/ip协议里得到的ip地址,apache收到请求并返回就是要返回到这个ip去。
- * HTTP_X_FORWARDED_FOR 要想透过代理服务器取得客户端的真实 IP 地址。
- * REMOTE_ADDR 取得客户端的 IP地址,如果客户端是使用代理服务器来访问,那取到的就是代理服务器的IP地址。
- */
- preg_match("/[\d\.]{7,15}/", $onlineip, $onlineipmatches);//使用正则匹配函数得到符合格式的ip地址,看看ip是不是点分段,7-15个数字之间
- $onlineip = $onlineipmatches[0] ? $onlineipmatches[0] : 'unknown';//如果上面获取到符合ip格式的ip就赋值给$onlineip
- unset($onlineipmatches);//立即清空 IP检查变量,方便下次处理
- $cachelost = (@include DISCUZ_ROOT.'./forumdata/cache/cache_settings.php') ? '' : 'settings';
- @extract($_DCACHE['settings']);
- /*
- * 读取论坛设置的缓存文件cache_settings.php
- * 如果能够正常读到,cachelost为空,既没有lost(丢失),这一步得到$_DCACHE['settings']数组(很重要,很多变量以后都要用到)
- * 这一段是获得./forumdata/cache/cache_settings.php(即缓存下的设置数组,并展开,方便以后的写法)
- * 将setting数组变量导入符号表,成为独立变量的形式,前面循环$$_key = $value处理$_POST等也是这个原理吧
- */
- if($gzipcompress && function_exists('ob_gzhandler') && !in_array(CURSCRIPT, array('attachment', 'wap')) && !$inajax) {
- ob_start('ob_gzhandler');
- } else {
- $gzipcompress = 0;
- ob_start();//输出缓存(开启输出缓冲)
- }
- /*
- * $gzipcompress(是否启用gzip)此变量来自@extract($_DCACHE['settings'])
- * 检查gzip是不是打开了,打开就用ob_gzhandler,没有就用ob_start。
- * 如果开启了gzip压缩,并且ob_gzhandler函数存在,并且当前脚本不是附件和wap,并且不是ajax,则开始使用ob_gzhandler压缩,否则只进行输出缓存而不压缩
- */
- if(!emptyempty($loadctrl) && substr(PHP_OS, 0, 3) != 'WIN') {
- if($fp = @fopen('/proc/loadavg', 'r')) {
- list($loadaverage) = explode(' ', fread($fp, 6));
- fclose($fp);
- if($loadaverage > $loadctrl) {
- header("HTTP/1.0 503 Service Unavailable");
- include DISCUZ_ROOT.'./include/serverbusy.htm';
- exit();
- }
- }
- }
- /*
- * 此段为在linux平台下处理负载平衡
- * 对于非win平台,进行服务器负载判断并进行操作。$loadctrl为负载临界值。
- */
- if(in_array(CURSCRIPT, array('index', 'forumdisplay', 'viewthread', 'post', 'topicadmin', 'register', 'archiver'))) {
- $cachelost .= (@include DISCUZ_ROOT.'./forumdata/cache/cache_'.CURSCRIPT.'.php') ? '' : ' '.CURSCRIPT;
- }
- /*
- * 继续判断当前dz缓存存在的状况,看看index, forumdisplay, viewthread这些文件是不是缓存了,有的话把它装到$cachelost这个变量中。
- * 对不同的脚本页面,引入不同的缓存文件
- */
- $db = new dbstuff;
- $db->connect($dbhost, $dbuser, $dbpw, $dbname, $pconnect, true, $dbcharset);
- $dbuser = $dbpw = $dbname = $pconnect = NULL;
- /*
- * 这里是初始化一个dbstull类的实例,也就是说前面的require_once DISCUZ_ROOT.'./include/db_'.$database.'.class.php';在这里用上了
- * 然后连接数据库并清理相关变量。由此可见DZ的安全性考虑真是牛到不行了。小气鬼! 呵呵!
- */
- $sid = daddslashes(($transsidstatus || CURSCRIPT == 'wap') && (isset($_GET['sid']) || isset($_POST['sid'])) ?
- (isset($_GET['sid']) ? $_GET['sid'] : $_POST['sid']) :
- (isset($_DCOOKIE['sid']) ? $_DCOOKIE['sid'] : ''));
- /*
- * 这里是个很长的三元运算符哦!对自定义的回话标识符进行过滤
- * 开启sid后,即使禁用cookie也可以登陆论坛$transsidstatus也是extract($_DCACHE['setting'])得到的,所以,熟悉cache_setting里面的变量才不会经常一头雾水
- * 如果开启了$sid且是wap,且从GET或POST传递来$sid,则从GET或者POST中取得,否则从cookie取得
- */
- $discuz_auth_key = md5($_DCACHE['settings']['authkey'].$_SERVER['HTTP_USER_AGENT']);
- /* 定义验证字符串,验证钥匙。'authkey' => '0ee545SPQKK3jOP2'也是extract($_DCACHE['setting'])定义的。
- * $_SERVER["HTTP_USER_AGENT"]获取客户端浏览器的型号,这个不是一定准确的,是可以随意伪造的
- */
- list($discuz_pw, $discuz_secques, $discuz_uid) = emptyempty($_DCOOKIE['auth']) ? array('', '', 0) : daddslashes(explode("\t", authcode($_DCOOKIE['auth'], 'DECODE')), 1);
- /*
- * 初始化变量:密码,安全问题,用户id,如果$_DCOOKIE['auth']为空,则初始化他们,否则解码并读取相应变量。
- * 对常用变量进行赋值。$discuz_secques为用户按安全验证回答的哈希值。authcode()根据$authkey对输入参数进行加密解密的操作。
- * 这一段是用来检查是不是$_DCOOKIE[‘auth’]存在,如果有的话就把其中存放的东西分别给$discuz_pw, $discuz_secques, $discuz_uid这三个变量,分别对应密码,提示问题和uid。
- */
- $newpm = $newpmexists = $sessionexists = $seccode = 0;//初始化用户的新消息,安全问题,验证码等变量
- $membertablefields = 'm.uid AS discuz_uid, m.username AS discuz_user, m.password AS discuz_pw, m.secques AS discuz_secques,
- m.adminid, m.groupid, m.groupexpiry, m.extgroupids, m.email, m.timeoffset, m.tpp, m.ppp, m.posts, m.digestposts,
- m.oltime, m.pageviews, m.credits, m.extcredits1, m.extcredits2, m.extcredits3, m.extcredits4, m.extcredits5,
- m.extcredits6, m.extcredits7, m.extcredits8, m.timeformat, m.dateformat, m.pmsound, m.sigstatus, m.invisible,
- m.lastvisit, m.lastactivity, m.lastpost, m.newpm, m.accessmasks, m.editormode, m.customshow, m.customaddfeed';
- /*
- * 将用户表中所有需要读取的字段放在一个字符串中,便于后面的sql语句调用
- */
- if($sid) {//如果变量$sid存在
- if($discuz_uid) {//如果cookie中有用户uid,则根据uid在数据库session表、member表中查询取出相关字段值
- $query = $db->query("SELECT s.sid, s.styleid, s.groupid='6' AS ipbanned, s.pageviews AS spageviews, s.lastolupdate, s.seccode, $membertablefields
- FROM {$tablepre}sessions s, {$tablepre}members m
- WHERE m.uid=s.uid AND s.sid='$sid' AND CONCAT_WS('.',s.ip1,s.ip2,s.ip3,s.ip4)='$onlineip' AND m.uid='$discuz_uid'
- AND m.password='$discuz_pw' AND m.secques='$discuz_secques'");
- } else {//如果cookie中没有用户uid,则仅仅从session表中取出相关信息
- $query = $db->query("SELECT sid, uid AS sessionuid, groupid, groupid='6' AS ipbanned, pageviews AS spageviews, styleid, lastolupdate, seccode
- FROM {$tablepre}sessions WHERE sid='$sid' AND CONCAT_WS('.',ip1,ip2,ip3,ip4)='$onlineip'");
- }
- /*
- * groupid='6':IP Banned 被禁止的ip
- * MySQL的几个实用字符串函数 :
- * concat()可以连接一个或者多个字符串,但是在连接字符串的时候,只要其中一个是NULL,那么将返回NULL
- * concat_ws()表示concat with separator,即有分隔符的字符串连接,在执行的时候,不会因为NULL值而返回NULL
- * group_concat()可用来行转列,
- * repeat()用来复制字符串,如select repeat('ab',2);'ab'表示要复制的字符串,2表示复制的份数
- */
- ?>