discuz源代码分析

发布时间:2025-12-09 16:41:31 浏览次数:4

 

2014年03月07日 11:51:34 囧 阅读数:3604 标签: 源代码代码分析discuz 收起

个人分类: PHP

第一个文件当然是分析./include/common.inc.php这个文件,这个是Discuz的核心中的核心,基本上每次操作都include到了这个文件,下面就分七段来分析这个文件:

Section One:

QUOTE:

CODE:

//定义PHP一些环境
error_reporting(0);
set_magic_quotes_runtime(0);

//设置Discuz开始的时间
$mtime = explode(' ', microtime());
$discuz_starttime = $mtime[1] + $mtime[0];

//定义一些常量
define('SYS_DEBUG', FALSE);
define('IN_DISCUZ', TRUE);
define('DISCUZ_ROOT', substr(dirname(__FILE__), 0, -7)); //获得绝对目录

//通用性
if(PHP_VERSION < '4.1.0') {
        $_GET = &$HTTP_GET_VARS;
        $_POST = &$HTTP_POST_VARS;
        $_COOKIE = &$HTTP_COOKIE_VARS;
        $_SERVER = &$HTTP_SERVER_VARS;
        $_ENV = &$HTTP_ENV_VARS;
        $_FILES = &$HTTP_POST_FILES;
}这 一段基本上就是设置一下错误报告,把magic_quotes这个sick家伙给关了,然后定一个开始的时间,这样我们在论坛底部看到的Process Time就是通过这个开始的时间和一个结束的时间的差来计算的,然后定义一个IN_DISCUZ为真,这个IN_DISCUZ常量的作用就是在其他inc 这样的包含文件中防止被非法引用,一旦没有这个常量的话就出现Access Denied这样的字样然后退出。然后获得Discuz运行的绝对目录。接下来是判断PHP 的版本是4.1 以下还是以上,因为PHP以4.1为一个分界线,在4.1以下以$HTTP_GET_VARS[‘xx’]这样的方式来得到get过来的值,而以后用$ _GET来得到get过来的值,这样做的目的是为了无论是什么样的PHP版本,都能用$_GET这样的方式得到,有通用性~!

QUOTE:

CODE:

require_once DISCUZ_ROOT.'./include/global.func.php';把include/global.inc.php引用进来,这个文件是Discuz的核心函数文件,包含了Discuz用到的很多通用的函数,可以说它就是一个大的通用函数库。

CODE:

define('ISROBOT', getrobot());
if(defined('NOROBOT') && ISROBOT) {
        exit(header("HTTP/1.1 403 Forbidden"));
}这里是定义一个ISROBOT常量,看看浏览者是什么东东,比方说如果浏览者是一个robot那么就直接来一个 403 Forbidden了……

CODE:

define('MAGIC_QUOTES_GPC', get_magic_quotes_gpc());
isset($_REQUEST['GLOBALS']) && exit('Access Error');
foreach(array('_COOKIE', '_POST', '_GET') as $_request) {
        foreach($$_request as $_key => $_value) {
                $_key{0} != '_' && $$_key = daddslashes($_value);
        }
}
(!MAGIC_QUOTES_GPC) && $_FILES = daddslashes($_FILES);此处是过滤提交的变量用的,提高安全性的用法。。

CODE:

$charset = $dbcharset = $forumfounders = $metakeywords = $extrahead = '';
$plugins = $hooks = $admincp = array();

require_once DISCUZ_ROOT.'./config.inc.php';

$_DCOOKIE = $_DSESSION = $_DCACHE = $_DPLUGIN = $advlist = array();

$prelength = strlen($cookiepre);
foreach($_COOKIE as $key => $val) {
        if(substr($key, 0, $prelength) == $cookiepre) {
                $_DCOOKIE[(substr($key, $prelength))] = MAGIC_QUOTES_GPC ? $val : daddslashes($val);
        }
}初 始化一些变量,然后引用config.inc.php这个配置文件,这样开始初始化程序的一些东西了。接下来的一个循环把$_COOKIE中的东西取出来 存到$_DCOOKIE这个数组中。注意:在登陆的时候Discuz会把登陆信息存放到$_COOKIE中去。在下面一段会有取出的代码。

CODE:

unset($prelength, $_request, $_key, $_value);
$timestamp = time();

if($attackevasive) {
        require_once DISCUZ_ROOT.'./include/security.inc.php';
}这一部分代码是提高安全用的,防一些非法的入侵,include/security.inc.php文件中就是这样一些检查。

CODE:

require_once DISCUZ_ROOT.'./include/db_'.$database.'.class.php';


$PHP_SELF = $_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME'];
$SCRIPT_FILENAME = str_replace('', '/', (isset($_SERVER['PATH_TRANSLATED']) ? $_SERVER['PATH_TRANSLATED'] : $_SERVER['SCRIPT_FILENAME']));
$boardurl = 'http://'.$_SERVER['HTTP_HOST'].preg_replace("///+(api|archiver|wap)?//*$/i", '', substr($PHP_SELF, 0, strrpos($PHP_SELF, '/'))).'/';

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'];
}第一行是把include/db_mysql.class.php引用进来,这个文件是一个数据库的类。我觉得是不是放在这里太早了点?
然后接下的作用就是得到自身的名称$PHP_SELF,自身的文件名字$SCRIPT_FILENAME,论坛的地址$boardurl,得到浏览者的一些信息,比方说ip地址,浏览器类型等等。

QUOTE:

CODE:

preg_match("/[/d/.]{7,15}/", $onlineip, $onlineipmatches);
$onlineip = $onlineipmatches[0] ? $onlineipmatches[0] : 'unknown';
unset($onlineipmatches);看看ip是不是点分段,7-15个 数字之间,用到了一个正则表达式,

CODE:

$cachelost = (@include DISCUZ_ROOT.'./forumdata/cache/cache_settings.php') ? '' : 'settings';
@extract($_DCACHE['settings']);这一段是获得./forumdata/cache/cache_settings.php(即缓存下的设置数组,并展开,方面以后的写法

CODE:

if($gzipcompress && function_exists('ob_gzhandler') && CURSCRIPT != 'wap') {
        ob_start('ob_gzhandler');
} else {
        $gzipcompress = 0;
        ob_start();
}检查gzip是不是打开了,打开就用ob_gzhandler,没有就用ob_start。

CODE:

if(!empty($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();
                }
        }
}看到了熟悉的service unavailable了吧?呵呵,平衡负载用的。

CODE:

if(defined('CURSCRIPT') && in_array(CURSCRIPT, array('index', 'forumdisplay', 'viewthread', 'post', 'blog', 'pm', 'topicadmin', 'register', 'archiver'))) {
        $cachelost .= (@include DISCUZ_ROOT.'./forumdata/cache/cache_'.CURSCRIPT.'.php') ? '' : ' '.CURSCRIPT;
}看看是不是index, forumdisplay, viewthread这些文件是不是缓存了,有的话把它装到$cachelost这个变量中。

 

论坛模式 推荐 收藏 分享给好友 推荐到圈子 管理

TAG:

 笨才 发布于2007-05-28 11:19:15

Section Four:

QUOTE:

CODE:

$db = new dbstuff;
$db->connect($dbhost, $dbuser, $dbpw, $dbname, $pconnect);
$dbhost = $dbuser = $dbpw = $dbname = $pconnect = NULL;好 了,这里是初始化一个dbstull类的实例,也就是说前面的include/db_mysql.class.php在这里用上了,所以我觉得那个 require_once早了点,放到这里的前面最合适了~!J下面就是连上mysql了,然后把几个配置的变量NULL掉,安全性考虑得真多,武装到牙 齿!

CODE:

$sid = daddslashes(($transsidstatus || (defined('CURSCRIPT') && CURSCRIPT == 'wap'))&& (isset($_GET['sid']) || isset($_POST['sid'])) ?
        (isset($_GET['sid']) ? $_GET['sid'] : $_POST['sid']) :
        (isset($_DCOOKIE['sid']) ? $_DCOOKIE['sid'] : ''));看看是不是后台设置了通过sid传输的那个东东,还有是不是通过wap访问的,还有是不是有sid这个东东在$_GET或$_POST这两个的任何一个中,以上结论都成立的话从GET中获得sid,不成立的话从$_DCOOKIE中获得。

CODE:

$discuz_auth_key = md5($_DCACHE['settings']['authkey'].$_SERVER['HTTP_USER_AGENT']);设置一个$discuz_auth_key,md5加密。。

CODE:

if(isset($_DCOOKIE['auth']) && $_DCOOKIE['auth']) {
        list($discuz_pw, $discuz_secques, $discuz_uid) = daddslashes(explode("/t", authcode($_DCOOKIE['auth'], 'DECODE')), 1);
        if(!is_numeric($discuz_uid) || !$discuz_uid) {
                clearcookies();
        }
} else {
        list($discuz_pw, $discuz_secques, $discuz_uid) = array('', '', 0);
}这一段是用来检查是不是$_DCOOKIE[‘auth’]存在,如果有的话就把其中存放的东西分别给$discuz_pw, $discuz_secques, $discuz_uid这三个变量,分别对应密码,提示问题和uid。

Section Five:

QUOTE:

CODE:

$newpm = $newpmexists = $sessionexists = $seccode = $bloguid = 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.xspacestatus, m.editormode, m.customshow';
if($sid) {
        if($discuz_uid) {
                $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 {
                $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'");
        }
        if($_DSESSION = $db->fetch_array($query)) {
                $sessionexists = 1;
                if(!empty($_DSESSION['sessionuid'])) {
                        $query = $db->query("SELECT $membertablefields
                                FROM {$tablepre}members m WHERE uid='$_DSESSION[sessionuid]'");
                        $_DSESSION = array_merge($_DSESSION, $db->fetch_array($query));
                }
        } else {
                $query = $db->query("SELECT sid, 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'");
                if($_DSESSION = $db->fetch_array($query)) {
                        clearcookies();
                        $sessionexists = 1;
                }
        }
}这一段是有蛮长的,不过看着长不代表它就难,第一行是初始化变量用的(无论何时用变量都要考虑初始化,要不然安全性不值得一提,一个get就完了)
接下来是判断是 不是有sid,有的话就从cdb_session表中取来,然后连接一下cdb_members表取出一些更具体的东西,具体是哪些东西?在$ membertablefields这个变量里面已经全面写出来了,对应数据库看吧,不看的话用英语猜猜得出的。。。在这里Discuz标记了一个 sessionexist变量,表示这个会员是在线的。

CODE:

if(!$sessionexists) {
        if($discuz_uid) {
                $query = $db->query("SELECT $membertablefields
                        FROM {$tablepre}members m WHERE m.uid='$discuz_uid' AND m.password='$discuz_pw' AND m.secques='$discuz_secques'");
                if(!($_DSESSION = $db->fetch_array($query))) {
                        clearcookies();
                }
        }要是不存在sid,不存在discuz_uid,那就肯定没有登陆了,清掉cookie,要是有$discuz_uid的话,还是从members表中取出信息存放到$_DSESSION数组中

CODE:

        if(ipbanned($onlineip)) $_DSESSION['ipbanned'] = 1;

        $_DSESSION['sid'] = random(6);
        $_DSESSION['seccode'] = random(6, 1);
}
$_DSESSION['dateformat'] = empty($_DSESSION['dateformat']) ? $_DCACHE['settings']['dateformat'] : $_DSESSION['dateformat'];
$_DSESSION['timeformat'] = empty($_DSESSION['timeformat']) ? $_DCACHE['settings']['timeformat'] : ($_DSESSION['timeformat'] == 1 ? 'h:i A' : 'H:i');
$_DSESSION['timeoffset'] = isset($_DSESSION['timeoffset']) && $_DSESSION['timeoffset'] != 9999 ? $_DSESSION['timeoffset'] : $_DCACHE['settings']['timeoffset'];这个是判断ip是不是在被阻止的list里,是的话就标记一下,用$_DSESSION[‘ipbanned’]标记的。再把一个随机的sid和seccode写到$_DSESSION数组。然后接下来是把日期,时间,时差写入$_DSESSION这个变量。

CODE:

$membertablefields = '';
@extract($_DSESSION);

$lastvisit = empty($lastvisit) ? $timestamp - 86400 : $lastvisit;
$timenow = array('time' => gmdate("$dateformat $timeformat", $timestamp + 3600 * $timeoffset),
        'offset' => ($timeoffset >= 0 ? ($timeoffset == 0 ? '' : '+'.$timeoffset) : $timeoffset));

if(PHP_VERSION > '5.1') {
        @date_default_timezone_set('Etc/GMT'.($timeoffset > 0 ? '-' : '+').(abs($timeoffset)));
}又见变量初始化,然后是把$_DESSION给展开,这样方便多了。接下来判断是不是有上次访问的时间,有的话就没事,没有的话就减去24小时。
接下来给一个现在的时间,这里有一个时间的问题,所以把时间加上时差乘上3600秒就得到当前时间了。
PHP 5 能处理时差了,所以Discuz在这里也设置了一下,想得真全面!!
Section Six:
[quote]

CODE:

$accessadd1 = $accessadd2 = $modadd1 = $modadd2 = '';
if(empty($discuz_uid) || empty($discuz_user)) {
        $discuz_user = $extgroupids = '';
        $discuz_uid = $adminid = $posts = $digestposts = $pageviews = $oltime = $invisible
                = $credits = $extcredits1 = $extcredits2 = $extcredits3 = $extcredits4
                = $extcredits5 = $extcredits6 = $extcredits7 = $extcredits8 = 0;
        $groupid = empty($groupid) || $groupid != 6 ? 7 : 6;

} else {
        $discuz_userss = $discuz_user;
        $discuz_user = addslashes($discuz_user);

        if($accessmasks) {
                $accessadd1 = ', a.allowview, a.allowpost, a.allowreply, a.allowgetattach, a.allowpostattach';
                $accessadd2 = "LEFT JOIN {$tablepre}access a ON a.uid='$discuz_uid' AND a.fid=f.fid";
        }

        if($adminid == 3) {
                $modadd1 = ', m.uid AS ismoderator';
                $modadd2 = "LEFT JOIN {$tablepre}moderators m ON m.uid='$discuz_uid' AND m.fid=f.fid";
        }
}

if($errorreport == 2 || ($errorreport == 1 && $adminid > 0)) {
        error_reporting(E_ERROR | E_WARNING | E_PARSE);
}首 先初始化变量,然后看看$discuz_user$和$discuz_user这两个变量是不是存在,不存在的话一系列的变量全部置0(没登陆当然不存在 版主权限管理员什么的),存在的话就来两个赋值,这就是为什么在Discuz UserGuide里面说$discuz_userss是没有过滤的,而$discuz_user是过滤掉的,能直接进行数据库操作的。
然后看看怎么样设置PHP 的出错级别,注意如果你的管理员的话,Discuz给的是一个更高的错误显示级别!

CODE:

define('FORMHASH', formhash());

$statstatus && require_once DISCUZ_ROOT.'./include/counter.inc.php';

$extra = isset($extra) && @preg_match("/^[&=;a-z0-9]+$/i", $extra) ? $extra : '';
$tpp = intval(empty($_DSESSION['tpp']) ? $topicperpage : $_DSESSION['tpp']);
$ppp = intval(empty($_DSESSION['ppp']) ? $postperpage : $_DSESSION['ppp']);这一段定义一个form hash,这个是通过global.func.php这个文件中的formhash()函数来的。然后看是不是要等到统计信息,要的话就引用./include/counter.inc.php这个文件,不要的话当然就不引用了。
$extra这个东东不知道做嘛用的。。。。
$tpp-threads per page每页的帖子数
$ppp-posts per page每页的回复数

CODE:

$rsshead = $navtitle = $navigation = '';


$_DSESSION['groupid'] = $groupid = empty($ipbanned) ? (empty($groupid) ? 7 : intval($groupid)) : 6;
if(!@include DISCUZ_ROOT.'./forumdata/cache/usergroup_'.$groupid.'.php') {
        $query = $db->query("SELECT type FROM {$tablepre}usergroups WHERE groupid='$groupid'");
        $grouptype = $db->result($query, 0);
        if(!empty($grouptype)) {
                $cachelost .= ' usergroup_'.$groupid;
        } else {
                $grouptype = 'member';
        }
}用来得到用户组的,首先看看是不是缓存中有,没有的话就访问数据库了,有的话当然就用缓存的。。

CODE:

if($passport_status && ($passport_status != 'shopex' || !$passport_shopex)) {
        $passport_forward = rawurlencode('http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
        $link_login = $passport_url.$passport_login_url.(strpos($passport_login_url, '?') === FALSE ? '?' : '&').'forward='.$passport_forward;
        $link_logout = $passport_url.$passport_logout_url.(strpos($passport_logout_url, '?') === FALSE ? '?' : '&').'forward='.$passport_forward;
        $link_register = $passport_url.$passport_register_url.(strpos($passport_register_url, '?') === FALSE ? '?' : '&').'forward='.$passport_forward;
} else {
        $link_login = 'logging.php?action=login';
        $link_logout = 'logging.php?action=logout&formhash='.FORMHASH;
        $link_register = 'register.php';
}Discuz通行证用的。主要就是得到一些应用程序的地址。

CODE:

if($discuz_uid && $_DSESSION) {
        if(!empty($groupexpiry) && $groupexpiry < $timestamp && (!defined('CURSCRIPT') || (CURSCRIPT != 'wap' && CURSCRIPT != 'member'))) {
                dheader("Location: {$boardurl}member.php?action=groupexpiry");
        } elseif($grouptype && $groupid != getgroupid($discuz_uid, array
                (
                'type' => $grouptype,
                'creditshigher' => $groupcreditshigher,
                'creditslower' => $groupcreditslower
                ), $_DSESSION)) {
                @extract($_DSESSION);
                $cachelost .= (@include DISCUZ_ROOT.'./forumdata/cache/usergroup_'.intval($groupid).'.php') ? '' : ' usergroup_'.$groupid;
        }
}这一段用来判断你的用户组是不是过期了的,如果过期了很不幸,就被告知用户组过期了。接下来如果没有过期的话就再展开一下$_DSESSION,因为此时的$_DSESSION包含了更多的东西了。

CODE:

if(!in_array($adminid, array(1, 2, 3))) {
        $alloweditpost = $alloweditpoll = $allowstickthread = $allowmodpost = $allowdelpost = $allowmassprune
                = $allowrefund = $allowcensorword = $allowviewip = $allowbanip = $allowedituser = $allowmoduser
                = $allowbanuser = $allowpostannounce = $allowviewlog = $disablepostctrl = $supe_allowpushthread = 0;
} elseif(isset($radminid) && $adminid != $radminid && $adminid != $groupid) {
        $cachelost .= (@include DISCUZ_ROOT.'./forumdata/cache/admingroup_'.intval($adminid).'.php') ? '' : ' admingroup_'.$groupid;
}这里是权限判断,如果你不是admin, moderator, super moderator,那么你的什么权限都没。。。

CODE:

$forum = array();
$auditstatuson = !empty($mod) && $mod == 'edit' && in_array($adminid, array(1, 2, 3)) && $allowmodpost ? true : false;

$tid = isset($tid) && is_numeric($tid) ? $tid : 0;
$fid = isset($fid) && is_numeric($fid) ? $fid : 0;
$typeid = isset($typeid) ? intval($typeid) : 0;

if(!empty($tid) || !empty($fid)) {
        if(empty($tid)) {
                $query = $db->query("SELECT f.fid, f.*, ff.* $accessadd1 $modadd1, f.fid AS fid
                        FROM {$tablepre}forums f
                        LEFT JOIN {$tablepre}forumfields ff ON ff.fid=f.fid $accessadd2 $modadd2
                        WHERE f.fid='$fid'");
                $forum = $db->fetch_array($query);
        } else {
                $query = $db->query("SELECT t.tid, t.closed,".(defined('SQL_ADD_THREAD') ? SQL_ADD_THREAD : '')." f.*, ff.* $accessadd1 $modadd1, f.fid AS fid
                        FROM {$tablepre}threads t
                        INNER JOIN {$tablepre}forums f ON f.fid=t.fid
                        LEFT JOIN {$tablepre}forumfields ff ON ff.fid=f.fid $accessadd2 $modadd2
                        WHERE t.tid='$tid'".($auditstatuson ? '' : " AND t.displayorder>='0'")." LIMIT 1");
                $forum = $db->fetch_array($query);
                $tid = $forum['tid'];
        }

        if($forum) {
                $fid = $forum['fid'];
                $forum['ismoderator'] = !empty($forum['ismoderator']) || $adminid == 1 || $adminid == 2 ? 1 : 0;
                foreach(array('postcredits', 'replycredits', 'threadtypes', 'digestcredits', 'postattachcredits', 'getattachcredits', 'supe_pushsetting') as $key) {
                        $forum[$key] = !empty($forum[$key]) ? unserialize($forum[$key]) : array();
                }
        } else {
                $fid = 0;
        }
}得到论坛信息用的,如果你是看forumdisplay页面和看viewthread在数据库中执行的东东不是一样的。这样能得到一点性能上的提升。然后往forum这个数组里面写一些东西进去,比方说:发帖子得到的分数,回复得到的分数,帖子分类的类型等…

CODE:

$styleid = intval(!empty($_GET['styleid']) ? $_GET['styleid'] :
                (!empty($_POST['styleid']) ? $_POST['styleid'] :
                (!empty($_DSESSION['styleid']) ? $_DSESSION['styleid'] :
                $_DCACHE['settings']['styleid'])));

$styleid = intval(isset($stylejump[$styleid]) ? $styleid : $_DCACHE['settings']['styleid']);

if(@!include DISCUZ_ROOT.'./forumdata/cache/style_'.intval(!empty($forum['styleid']) ? $forum['styleid'] : $styleid).'.php') {
        $cachelost .= (@include DISCUZ_ROOT.'./forumdata/cache/style_'.($styleid = $_DCACHE['settings']['styleid']).'.php') ? '' : ' style_'.$styleid;
}这里是得到论坛风格的地方,可以看到第一行的赋值Disuz用尽一切可能得到一个合理的风格styleid,呵呵,然后再来一行,看看是不是你在论坛底部切换了一下风格,有的话就override一下,把这个风格作为你的当前风格。
如果没有缓存的话,再往cachelost这个里面写点东西。

CODE:

if($cachelost) {
        require_once DISCUZ_ROOT.'./include/cache.func.php';
        updatecache();
        dexit('Cache List: '.$cachelost.'<br>Caches successfully created, please refresh.');
}如果cachelost中有东西的话,那么就调用include/cache.func.php,并用这个文件里面定义的updatecache();来更新缓存,并强制访问者刷新论坛,使缓存立即生效。

CODE:

if(!defined('CURSCRIPT') || CURSCRIPT != 'wap') {
        if($nocacheheaders) {
                @dheader("Expires: 0");
                @dheader("Cache-Control: private, post-check=0, pre-check=0, max-age=0", FALSE);
                @dheader("Pragma: no-cache");
        }
        if($headercharset) {
                @dheader('Content-Type: text/html; charset='.$charset);
        }
        if(empty($_DCOOKIE['sid']) || $sid != $_DCOOKIE['sid']) {
                dsetcookie('sid', $sid, 604800);
        }
}这 里是设置网页的header用的,通过判断是不是wap或者是不是有CURSCRIPT这个常量(注:CURSCRIPT代表了当前执行的script, 比方说forumdisplay.php中就会有define(‘CURSCRIPT’, ‘forumdisplay’);这样的定义),下一行是说sid过期了,再生成一个,dsetcookie()这个函数是. /include/global.func.php这个文件中定义的。

CODE:

if($cronnextrun && $cronnextrun <= $timestamp) {
        require_once DISCUZ_ROOT.'./include/cron.func.php';
        runcron();
}呵呵,后台的计划任务来了。。。。当然,这种情况是要至少有一个会员访问论坛,如果说没有人访问,你再设置也没有用,PHP的一大痛处,不能自己执行…

CODE:

if(isset($plugins['include']) && is_array($plugins['include'])) {
        foreach($plugins['include'] as $include) {
                if(!$include['adminid'] || ($include['adminid'] && $include['adminid'] >= $adminid)) {
                        @include_once DISCUZ_ROOT.'./plugins/'.$include['script'].'.inc.php';
                }
        }
}这里的加载一些插件的文件。

CODE:

if((!empty($_DCACHE['advs']) || $globaladvs || $redirectadvs) && !defined('IN_ADMINCP')) {
        require_once DISCUZ_ROOT.'./include/advertisements.inc.php';
}这里是看看是不是在论坛的cache中有广告的存在(注:$_DCACHE这个数组中存放的是论坛的一些设置之类的缓存),有广告的话就引用广告的文件。

CODE:

if(isset($allowvisit) && $allowvisit == 0 && !(defined('CURSCRIPT') && CURSCRIPT == 'member' && $action == 'groupexpiry')) {
        showmessage('user_banned', NULL, 'HALTED');
} elseif(!((defined('CURSCRIPT') && in_array(CURSCRIPT, array('logging', 'wap', 'seccode'))) || $adminid == 1)) {
        if($bbclosed) {
                clearcookies();
                $closedreason = $db->result($db->query("SELECT value FROM {$tablepre}settings WHERE variable='closedreason'"), 0);
                showmessage($closedreason ? $closedreason : 'board_closed', NULL, 'NOPERM');
        }
        periodscheck('visitbanperiods');
}论 坛的安全访问设置。一个是不允许访问,另一个是访问到了member.php?action=groupexpiry的话,就说用户被ban了。然后就是 检查是不是论坛关闭了,可以看也论坛关闭对管理员没有影响。接下来检查是不是禁的时间到了…periodscheck()函数就是这个用的。如果没到是会 被showmessage说没有访问权限的,具体看include/global.func.php这个文件中的定义。当然,下一期我就会分析这个文件。

CODE:

if((!empty($fromuid) || !empty($fromuser)) && ($creditspolicy['promotion_visit'] || $creditspolicy['promotion_register'])) {
        require_once DISCUZ_ROOT.'/include/promotion.inc.php';
}这个当然是推荐注册用的。应该没看错,呵呵。

CODE:

$rssauth = $rssstatus && $discuz_uid ? rawurlencode(authcode("$discuz_uid/t".($fid ? $fid : '')."/t".substr(md5($discuz_pw.$discuz_secques), 0, 8), 'ENCODE', md5($_DCACHE['settings']['authkey']))) : '0';Rss检查

CODE:

//同样,防止非法引用用的。
if(!defined('IN_DISCUZ')) {
        exit('Access Denied');
}

CODE:

/**
* 这个函数看着很长,其实要实现的功能很少,就是通过ip地址返回一个对应的地理位置
* @para string $ip //给定的ip,要符合点分十进制

* @return string 
*/
function convertip($ip) {
        if(!preg_match("/^/d{1,3}/./d{1,3}/./d{1,3}/./d{1,3}$/", $ip)) {
                return '';
        }

        if($fd = @fopen(DISCUZ_ROOT.'./ipdata/wry.dat', 'rb')) {

                $ip = explode('.', $ip);
                $ipNum = $ip[0] * 16777216 + $ip[1] * 65536 + $ip[2] * 256 + $ip[3];

                $DataBegin = fread($fd, 4);
                $DataEnd = fread($fd, 4);
                $ipbegin = implode('', unpack('L', $DataBegin));
                if($ipbegin < 0) $ipbegin += pow(2, 32);
                $ipend = implode('', unpack('L', $DataEnd));
                if($ipend < 0) $ipend += pow(2, 32);
                $ipAllNum = ($ipend - $ipbegin) / 7 + 1;

                $BeginNum = 0;
                $EndNum = $ipAllNum;

                while($ip1num > $ipNum || $ip2num < $ipNum) {
                        $Middle= intval(($EndNum + $BeginNum) / 2);

                        fseek($fd, $ipbegin + 7 * $Middle);
                        $ipData1 = fread($fd, 4);
                        if(strlen($ipData1) < 4) {
                                fclose($fd);
                                return '- System Error';
                        }
                        $ip1num = implode('', unpack('L', $ipData1));
                        if($ip1num < 0) $ip1num += pow(2, 32);

                        if($ip1num > $ipNum) {
                                $EndNum = $Middle;
                                continue;
                        }

                        $DataSeek = fread($fd, 3);
                        if(strlen($DataSeek) < 3) {
                                fclose($fd);
                                return '- System Error';
                        }
                        $DataSeek = implode('', unpack('L', $DataSeek.chr(0)));
                        fseek($fd, $DataSeek);
                        $ipData2 = fread($fd, 4);
                        if(strlen($ipData2) < 4) {
                                fclose($fd);
                                return '- System Error';
                        }
                        $ip2num = implode('', unpack('L', $ipData2));
                        if($ip2num < 0) $ip2num += pow(2, 32);

                        if($ip2num < $ipNum) {
                                if($Middle == $BeginNum) {
                                        fclose($fd);
                                        return '- Unknown';
                                }
                                $BeginNum = $Middle;
                        }
                }

                $ipFlag = fread($fd, 1);
                if($ipFlag == chr(1)) {
                        $ipSeek = fread($fd, 3);
                        if(strlen($ipSeek) < 3) {
                                fclose($fd);
                                return '- System Error';
                        }
                        $ipSeek = implode('', unpack('L', $ipSeek.chr(0)));
                        fseek($fd, $ipSeek);
                        $ipFlag = fread($fd, 1);
                }

                if($ipFlag == chr(2)) {
                        $AddrSeek = fread($fd, 3);
                        if(strlen($AddrSeek) < 3) {
                                fclose($fd);
                                return '- System Error';
                        }
                        $ipFlag = fread($fd, 1);
                        if($ipFlag == chr(2)) {
                                $AddrSeek2 = fread($fd, 3);
                                if(strlen($AddrSeek2) < 3) {
                                        fclose($fd);
                                        return '- System Error';
                                }
                                $AddrSeek2 = implode('', unpack('L', $AddrSeek2.chr(0)));
                                fseek($fd, $AddrSeek2);
                        } else {
                                fseek($fd, -1, SEEK_CUR);
                        }

                        while(($char = fread($fd, 1)) != chr(0))
                                $ipAddr2 .= $char;

                        $AddrSeek = implode('', unpack('L', $AddrSeek.chr(0)));
                        fseek($fd, $AddrSeek);

                        while(($char = fread($fd, 1)) != chr(0))
                                $ipAddr1 .= $char;
                } else {
                        fseek($fd, -1, SEEK_CUR);
                        while(($char = fread($fd, 1)) != chr(0))
                                $ipAddr1 .= $char;

                        $ipFlag = fread($fd, 1);
                        if($ipFlag == chr(2)) {
                                $AddrSeek2 = fread($fd, 3);
                                if(strlen($AddrSeek2) < 3) {
                                        fclose($fd);
                                        return '- System Error';
                                }
                                $AddrSeek2 = implode('', unpack('L', $AddrSeek2.chr(0)));
                                fseek($fd, $AddrSeek2);
                        } else {
                                fseek($fd, -1, SEEK_CUR);
                        }
                        while(($char = fread($fd, 1)) != chr(0))
                                $ipAddr2 .= $char;
                }
                fclose($fd);

                if(preg_match('/http/i', $ipAddr2)) {
                        $ipAddr2 = '';
                }
                $ipaddr = "$ipAddr1 $ipAddr2";
                $ipaddr = preg_replace('/CZ88/.NET/is', '', $ipaddr);
                $ipaddr = preg_replace('/^/s*/is', '', $ipaddr);
                $ipaddr = preg_replace('//s*$/is', '', $ipaddr);
                if(preg_match('/http/i', $ipaddr) || $ipaddr == '') {
                        $ipaddr = '- Unknown';
                }

                return '- '.$ipaddr;

        } else {

                $datadir = DISCUZ_ROOT.'./ipdata/';
                $ip_detail = explode('.', $ip);
                if(file_exists($datadir.$ip_detail[0].'.txt')) {
                        $ip_fdata = @fopen($datadir.$ip_detail[0].'.txt', 'r');
                } else {
                        if(!($ip_fdata = @fopen($datadir.'0.txt', 'r'))) {
                                return '- Invalid IP data file';
                        }
                }
                for($i = 0; $i <= 3; $i++) {
                        $ip_detail[$i] = sprintf('%03d', $ip_detail[$i]);
                }
                $ip = join('.', $ip_detail);
                do {
                        $ip_data = fgets($ip_fdata, 200);
                        $ip_data_detail = explode('|', $ip_data);
                        if($ip >= $ip_data_detail[0] && $ip <= $ip_data_detail[1]) {
                                fclose($ip_fdata);
                                return '- '.$ip_data_detail[2].$ip_data_detail[3];
                        }
                } while(!feof($ip_fdata));
                fclose($ip_fdata);
                return '- UNKNOWN';

        }

}

CODE:

/**
* 目前发现这个函数在两个地方用到,search.php和digest.php,这个函数的用处是把从数据库中得到的帖子处理一下,然后给前端显示。
* @para array $thread //从数据库中取出的帖子原始数据

* @return array
*/
function procthread($thread) {
        global $dateformat, $timeformat, $timeoffset, $ppp, $colorarray;

        if(empty($colorarray)) {
                $colorarray = array('', 'red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'purple', 'gray');
        }

        $thread['forumname'] = $GLOBALS['_DCACHE']['forums'][$thread['fid']]['name'];
        $thread['dateline'] = gmdate($dateformat, $thread['dateline'] + $timeoffset * 3600);
        $thread['lastpost'] = gmdate("$dateformat $timeformat", $thread['lastpost'] + $timeoffset * 3600);
        $thread['lastposterenc'] = rawurlencode($thread['lastposter']);

        if($thread['replies'] > $thread['views']) {
                $thread['views'] = $thread['replies'];
        }

        $postsnum = $thread['replies'] + 1;
        $pagelinks = '';
        if($postsnum  > $ppp) {
                $posts = $postsnum;
                $topicpages = ceil($posts / $ppp);
                for($i = 1; $i <= $topicpages; $i++) {
                        $pagelinks .= '<a href="viewthread.php?tid='.$thread['tid'].'&page='.$i.'" target="_blank">'.$i.'</a> ';
                        if($i == 6) {
                                $i = $topicpages + 1;
                        }
                }
                if($topicpages > 6) {
                        $pagelinks .= ' .. <a href="viewthread.php?tid='.$thread['tid'].'&page='.$topicpages.'" target="_blank">'.$topicpages.'</a> ';
                }
                $thread['multipage'] = '   ( <img src="'.IMGDIR.'/multipage.gif" class="absmiddle" border="0" alt="" /> '.$pagelinks.')';
        } else {
                $thread['multipage'] = '';
        }

        if($thread['highlight']) {
                $string = sprintf('%02d', $thread['highlight']);
                $stylestr = sprintf('%03b', $string[0]);

                $thread['highlight'] = 'style="';
                $thread['highlight'] .= $stylestr[0] ? 'font-weight: bold;' : '';
                $thread['highlight'] .= $stylestr[1] ? 'font-style: italic;' : '';
                $thread['highlight'] .= $stylestr[2] ? 'text-decoration: underline;' : '';
                $thread['highlight'] .= $string[1] ? 'color: '.$colorarray[$string[1]] : '';
                $thread['highlight'] .= '"';
        } else {
                $thread['highlight'] = '';
        }

        if($thread['attachment']) {
                require_once DISCUZ_ROOT.'./include/attachment.func.php';
                $thread['attachment'] = attachtype($thread['attachment']).' ';
        } else {
                $thread['attachment'] = '';
        }

        return $thread;
}

CODE:

/**
* 用来更新点击数的
* @para string $table //用来更新的数据表
* @para string $idcol //用来更新的数据的id
* @para string $viewscol //用来更新的列
* @para string $logfile //日志文件,点击是写入日志的
*/
function updateviews($table, $idcol, $viewscol, $logfile) {
        global $db, $tablepre;

        $viewlog = $viewarray = array();
        if(@$viewlog = file($logfile = DISCUZ_ROOT.$logfile)) {
                @unlink($logfile);
                $viewlog = array_count_values($viewlog);
                foreach($viewlog as $id => $views) {
                        $viewarray[$views] .= ($id > 0) ? ','.intval($id) : '';
                }
                foreach($viewarray as $views => $ids) {
                        $db->query("UPDATE $tablepre$table SET $viewscol=$viewscol+$views WHERE $idcol IN (0$ids)", 'UNBUFFERED');
                }
        }
}

CODE:

/**
* 给版主的工作写入日志的
* @para array $thread //对哪个帖子的操作
* @para string $action //进行的什么操作
*
*/
function modlog($thread, $action) {
        global $discuz_user, $adminid, $onlineip, $timestamp, $forum, $reason;
        writelog('modslog', dhtmlspecialchars("$timestamp/t$discuz_user/t$adminid/t$onlineip/t$forum[fid]/t$forum[name]/t$thread[tid]/t$thread[subject]/t$action/t$reason"));
}

CODE:

/**
* 检查是不是写了理由

*/
function checkreasonpm() {
        global $reason;
        $reason = trim(strip_tags($reason));
        if(($GLOBALS['reasonpm'] == 1 || $GLOBALS['reasonpm'] == 3) && !$reason) {
                showmessage('admin_reason_invalid');
        }
}

CODE:

/**
* 发送操作理由报告给帖子作者
* @para array $var
* @para string $item
*/
function sendreasonpm($var, $item) {
        global $$var;
        ${$var}['subject'] = strtr(${$var}['subject'], array_flip(get_html_translation_table(HTML_ENTITIES)));
        ${$var}['dateline'] = gmdate($GLOBALS['_DCACHE']['settings']['dateformat'].' '.$GLOBALS['_DCACHE']['settings']['timeformat'], ${$var}['dateline'] + ($GLOBALS['timeoffset'] * 3600));
        sendpm(${$var}['authorid'], $item.'_subject', $item.'_message');
}

CODE:

/**
* 得到一个下拉列表,这个列表是版主操作理由选择

* @return string $select 
*/
function modreasonselect() {
        global $_DCACHE;
        if(!isset($_DCACHE['modreasons']) || !is_array($_DCACHE['modreasons'])) {
                @include DISCUZ_ROOT.'./forumdata/cache/cache_topicadmin.php';
        }
        $select = '';
        foreach($_DCACHE['modreasons'] as $reason) {
                $select .= $reason ? '<option value="'.dhtmlspecialchars($reason).'">'.$reason.'</option>' : '<option value="">--------</option>';
        }
        return $select;
}

CODE:

/**
* 登陆检查,看看是不是在十五分钟已经登陆五次了
*
* @return int
*/
function logincheck() {
        global $db, $tablepre, $onlineip, $timestamp;
        $query = $db->query("SELECT count, lastupdate FROM {$tablepre}failedlogins WHERE ip='$onlineip'");
        if($login = $db->fetch_array($query)) {
                if($timestamp - $login['lastupdate'] > 900) {
                        return 3;
                } elseif($login['count'] < 5) {
                        return 2;
                } else {
                        return 0;
                }
        } else {
                return 1;
        }
}

CODE:

/**
* 和上面的那个函数(logincheck)是配对的,看是哪种方式登陆的
* @para int $permission

*/
function loginfailed($permission) {
        global $db, $tablepre, $onlineip, $timestamp;
        switch($permission) {
                case 1:        $db->query("REPLACE INTO {$tablepre}failedlogins (ip, count, lastupdate) VALUES ('$onlineip', '1', '$timestamp')");
                        break;
                case 2: $db->query("UPDATE {$tablepre}failedlogins SET count=count+1, lastupdate='$timestamp' WHERE ip='$onlineip'");
                        break;
                case 3: $db->query("UPDATE {$tablepre}failedlogins SET count='1', lastupdate='$timestamp' WHERE ip='$onlineip'");
                        $db->query("DELETE FROM {$tablepre}failedlogins WHERE lastupdate<$timestamp-901", 'UNBUFFERED');
                        break;
        }
}

 笨才 发布于2007-05-28 12:07:07

Discuz!源代码分析系列--./include/template.func.php(模板)
本帖不仅仅是分析文件,还把Discuz中模板解析这一原理分析了一下。

我记得我刚开始学PHP的时候,对模板解析实在是觉得很奇怪,不知道这个原理怎么实现的,后来看书看多了也明白有一个著名的Smarty在那,曾经也用过 一段,不过感觉不是很好,就开始分析Discuz的模板技术是怎么实现的了,然后我把这个模板解析的代码分离出来了,觉得很好用,用了一段时间, Discuz的模板解析是用正则表达式替换一些模板中的规定的语言标记,然后呢,写到forumdata/templates中,再用include引用 到index, forumdisplay等等中,和smarty的原理基本上相同,只是功能没有smarty那么多,smarty内建了一个cache来着…连个 User Guide都几百页…
  呵呵,不过现在基本上两个都没 用过,正则表达式好是好用,不过正则的效率不是很高,以前看PHP技术文档的时候说能不用正则就尽量不要用,那东西烦着,现在做什么项目基本上用的是框 架,MVC的模式,View中的代码一般不用模板解析出来,混杂php代码在里面,也觉得不错,OOP的开发效率比较高,很多地方重用代码是很开心的~!

  Discuz的模板解析要分析出来只要用到两个文件:./include/global.func.php和. /include/template.func.php,global只要一个函数就够了,template要全部的文件下面我就分开讲一下,会比较详 细,大家耐心看:

Section One--./include/global.func.php---->template function

CODE:

function template($file, $templateid = 0, $tpldir = '') {
        global $tplrefresh;

        $tpldir = $tpldir ? $tpldir : TPLDIR;
        $templateid = $templateid ? $templateid : TEMPLATEID;

        $tplfile = DISCUZ_ROOT.'./'.$tpldir.'/'.$file.'.htm';
        $objfile = DISCUZ_ROOT.'./forumdata/templates/'.$templateid.'_'.$file.'.tpl.php';
        if(TEMPLATEID != 1 && $templateid != 1 && !file_exists($tplfile)) {
                return template($file, 1, './templates/default/');
        }
        if($tplrefresh == 1 || ($tplrefresh > 1 && substr($GLOBALS['timestamp'], -1) > $tplrefresh)) {
                if(@filemtime($tplfile) > @filemtime($objfile)) {
                        require_once DISCUZ_ROOT.'./include/template.func.php';
                        parse_template($file, $templateid, $tpldir);
                }
        }
        return $objfile;
}这个函数一共有三个传入参数:
$file 表示模板名
$templateid 表示模板id
$tpldir 表示模板目录

QUOTE:

CODE:

global $tplrefresh;这个是把$tplrefresh作为一个全局变量,tplrefresh表示是不是刷新模板

QUOTE:

CODE:

        $tpldir = $tpldir ? $tpldir : TPLDIR;
        $templateid = $templateid ? $templateid : TEMPLATEID;给$tpldir和$templateid赋值,如果没有传进来就用常量TPLDIR和TEMPLATEID给它们值

QUOTE:

CODE:

        $tplfile = DISCUZ_ROOT.'./'.$tpldir.'/'.$file.'.htm';
        $objfile = DISCUZ_ROOT.'./forumdata/templates/'.$templateid.'_'.$file.'.tpl.php';这里是把$tplfile(表示的是模板文件)和$objfile(表示的是要编译成的文件)赋值

QUOTE:

CODE:

        if(TEMPLATEID != 1 && $templateid != 1 && !file_exists($tplfile)) {
                return template($file, 1, './templates/default/');
        }防止TEMPLATEID不等于1且$templateid不等于1,而且模板文件不存在导致空白问题。
这里也可以算一个迭代,也可以不算,就是把1作为第二个参数再调用函数本身。

QUOTE:

CODE:

        if($tplrefresh == 1 || ($tplrefresh > 1 && substr($GLOBALS['timestamp'], -1) > $tplrefresh)) {
                if(@filemtime($tplfile) > @filemtime($objfile)) {
                        require_once DISCUZ_ROOT.'./include/template.func.php';
                        parse_template($file, $templateid, $tpldir);
                }
        }
        return $objfile;很 巧妙的一段,Discuz的模板缓存就体现在这里,如果你没打开模板刷新的话(config.inc.php->$tplrefresh=0),这 里就直接返回一个$objfile了,而这个文件是你第一次建论坛的时候就写入的,如果你不改模板的话,关了是能提高相当一部分效率的!反之,如果你打开 了模板刷新的话就接着判断是不是模板文件的建立时间大于forumdata/templates下的文件,是的话就引用. /include/template.func.php写入模板文件到forumdata/templates中,否则的话还是直接返回一个已经编译好的 模板文件。

 

 笨才 发布于2007-05-28 12:08:37

关于template.func.php文件中函数的分析在下面:

CODE:

//防止非法引用
if(!defined('IN_DISCUZ')) {
        exit('Access Denied');
}

CODE:

/**
* 这个是关键模板解析函数,通过正则表达式来替换掉模板中的相关语句,使之变成标准的php语句,写入缓存(./forumdata/template),从而include到页面中显示出来
* @para string $file //解析的模板名,只要文件名就可以了,会自动加上htm,如果有缓存的话就变成"模板id_文件名.tpl.php"
* @para int $templateid //模板的id
* @para string $tpldir //模板所在的目录
*
*/

function parse_template($file, $templateid, $tpldir) {
        global $language;

        $nest = 5;
        $tplfile = DISCUZ_ROOT."./$tpldir/$file.htm";
        $objfile = DISCUZ_ROOT."./forumdata/templates/{$templateid}_$file.tpl.php";

        if(!@$fp = fopen($tplfile, 'r')) {
                dexit("Current template file './$tpldir/$file.htm' not found or have no access!");
        } elseif(!include_once language('templates', $templateid, $tpldir)) {
                dexit("<br>Current template pack do not have a necessary language file 'templates.lang.php' or have syntax error!");
        }

        $template = fread($fp, filesize($tplfile));
        fclose($fp);

        $var_regexp = "((///$[a-zA-Z_/x7f-/xff][a-zA-Z0-9_/x7f-/xff]*)(/[[a-zA-Z0-9_/-/./"/'/[/]/$/x7f-/xff]+/])*)";
        $const_regexp = "([a-zA-Z_/x7f-/xff][a-zA-Z0-9_/x7f-/xff]*)";

        $template = preg_replace("/([/n/r]+)/t+/s", "//1", $template);
        $template = preg_replace("//</!/-/-/{(.+?)/}/-/-/>/s", "{//1}", $template);
        $template = preg_replace("//{lang/s+(.+?)/}/ies", "languagevar('//1')", $template);
        $template = preg_replace("//{faq/s+(.+?)/}/ies", "faqvar('//1')", $template);
        $template = str_replace("{LF}", "<?=/"//n/"?>", $template);

        $template = preg_replace("//{(///$[a-zA-Z0-9_/[/]/'/"/$/./x7f-/xff]+)/}/s", "<?=//1?>", $template);
        $template = preg_replace("/$var_regexp/es", "addquote('<?=//1?>')", $template);
        $template = preg_replace("//</?/=/</?/=$var_regexp/?/>/?/>/es", "addquote('<?=//1?>')", $template);

        $template = "<? if(!defined('IN_DISCUZ')) exit('Access Denied'); ?>/n$template";
        $template = preg_replace("/[/n/r/t]*/{template/s+([a-z0-9_]+)/}[/n/r/t]*/is", "/n<? include template('//1'); ?>/n", $template);
        $template = preg_replace("/[/n/r/t]*/{template/s+(.+?)/}[/n/r/t]*/is", "/n<? include template(//1); ?>/n", $template);
        $template = preg_replace("/[/n/r/t]*/{eval/s+(.+?)/}[/n/r/t]*/ies", "stripvtags('/n<? //1 ?>/n','')", $template);
        $template = preg_replace("/[/n/r/t]*/{echo/s+(.+?)/}[/n/r/t]*/ies", "stripvtags('/n<? echo //1; ?>/n','')", $template);
        $template = preg_replace("/[/n/r/t]*/{elseif/s+(.+?)/}[/n/r/t]*/ies", "stripvtags('/n<? } elseif(//1) { ?>/n','')", $template);
        $template = preg_replace("/[/n/r/t]*/{else/}[/n/r/t]*/is", "/n<? } else { ?>/n", $template);

        for($i = 0; $i < $nest; $i++) {
                $template = preg_replace("/[/n/r/t]*/{loop/s+(/S+)/s+(/S+)/}[/n/r]*(.+?)[/n/r]*/{//loop/}[/n/r/t]*/ies", "stripvtags('/n<? if(is_array(//1)) { foreach(//1 as //2) { ?>','/n//3/n<? } } ?>/n')", $template);
                $template = preg_replace("/[/n/r/t]*/{loop/s+(/S+)/s+(/S+)/s+(/S+)/}[/n/r/t]*(.+?)[/n/r/t]*/{//loop/}[/n/r/t]*/ies", "stripvtags('/n<? if(is_array(//1)) { foreach(//1 as //2 => //3) { ?>','/n//4/n<? } } ?>/n')", $template);
                $template = preg_replace("/[/n/r/t]*/{if/s+(.+?)/}[/n/r]*(.+?)[/n/r]*/{//if/}[/n/r/t]*/ies", "stripvtags('/n<? if(//1) { ?>','/n//2/n<? } ?>/n')", $template);
        }

        $template = preg_replace("//{$const_regexp/}/s", "<?=//1?>", $template);
        $template = preg_replace("/ /?/>[/n/r]*/</? /s", " ", $template);

        if(!@$fp = fopen($objfile, 'w')) {
                dexit("Directory './forumdata/templates/' not found or have no access!");
        }

        $template = preg_replace("//"(http)?[/w/.//:]+/?[^/"]+?&[^/"]+?/"/e", "transamp('//0')", $template);
        $template = preg_replace("//<script[^/>]*?src=/"(.+?)/".*?/>/s*/<//script/>/ise", "stripscriptamp('//1')", $template);

        flock($fp, 2);
        fwrite($fp, $template);
        fclose($fp);
}

CODE:

/**
* 几个替换,过滤掉一些东西
* @para string $str

* @return string
*/
        
function transamp($str) {
        $str = str_replace('&', '&', $str);
        $str = str_replace('&', '&', $str);
        $str = str_replace('/"', '"', $str);
        return $str;
}

CODE:

/**
* 从正则表达式来看是给ubb代码去掉一个/符号的,应该是为安全性着想的
* @para string $val
*
* @return string
*/

function addquote($var) {
        return str_replace("///"", "/"", preg_replace("//[([a-zA-Z0-9_/-/./x7f-/xff]+)/]/s", "['//1']", $var));
}

CODE:

/**
* 把一个字符用语言包中的来替换,没有找到的话就用!string来替换
* @para string $val
*
* @return string
*/

function languagevar($var) {
        if(isset($GLOBALS['language'][$var])) {
                return $GLOBALS['language'][$var];
        } else {
                return "!$var!";
        }
}

CODE:

/**
* FAQ中把id和关建字用一个有效的链接来替换
* @para array $val
*
* @return string
*/

function faqvar($var) {
        global $_DCACHE;
        include_once DISCUZ_ROOT.'./forumdata/cache/cache_faqs.php';

        if(isset($_DCACHE['faqs'][$var])) {
                return '<a href="faq.php?action=message&id='.$_DCACHE['faqs'][$var]['id'].'" target="_blank">'.$_DCACHE['faqs'][$var]['keyword'].'</a>';
        } else {
                return "!$var!";
        }
}

CODE:

/**
* 去掉自定义的一些tag
* @para string $expr
* @para string $statement
* @return string
*/

function stripvtags($expr, $statement) {
        $expr = str_replace("///"", "/"", preg_replace("//</?/=(///$.+?)/?/>/s", "//1", $expr));
        $statement = str_replace("///"", "/"", $statement);
        return $expr.$statement;
}

CODE:

/**
* 作用是把&符号的html编码形式换成&,然后换成javascript引用的形式。
* @para string $s
*
* @return string
*/

function stripscriptamp($s) {
        $s = str_replace('&', '&', $s);
        return "<script src=/"$s/" type=/"text/javascript/"></script>";
}

 笨才 发布于2007-05-28 12:13:35

Discuz!5.5源代码分析系列-AJAX工作原理,底层代码分析
Discuz的ajax原理实际上是很简单的,当然,说到ajax肯定是用XMLHttpRequest这个对象了,不过我曾经也看到国外的牛人写过一篇 文章叫做不用XMLHttpRequest对象来实现ajax,而且还解决了跨域的问题,真是大开了眼界~!好了,说正事,Dz的ajax用到的文件不是 很多,列举如下:

QUOTE:

./include/javascript/common.js  
这个文件把Discuz用到的许多javascript的代码(主要为函数和浏览者的浏览器的判断等等)

QUOTE:

./include/javascript/ajax.js 
不用看就知道是 创建一个可用的XMLHttpRequest对象用的(由于XMLHttpRequest在各个浏览器的创建不同,因此要对各个不同的浏览器进行不同的创 建,还好prototype.js中有一个通用的东东。当然,这里也封装了get, post这类的函数,刚发现5.5封装了更多的东西,有ajaxmenu,updatesecqaa,ignorepm。

QUOTE:

./ajax.php
这个文件是ajax的后台处理文件,作用相当于MVC三层中的M(Model)和C(Controller)层,因为它负责和后台数据库通信并返回和处理 一些信息,比如新的用户在注册的时候通过XMLHttpRequest向ajax.php发送一个请求,这个请求是通过带参数用GET发出去, ajax.php检查数据库中用户名是不是存在,并用global.func.php中定义的ajaxtemplate调用 showmessage_ajax模板返回一个XML文档,ajax这个对象实际上是解析这个xml文档的,具体的解析就是返回root这个元素中的 CDATA中的值,再用register.htm中的javascript调用p对象的innerhtml方法给login模板中的一个p给前端 用户提示用户是不是存在。

QUOTE:

CODE:

var Ajaxs = new Array();
function Ajax(recvType, statusId) {
        var aj = new Object();
        aj.statusId = statusId ? document.getElementById(statusId) : null;
        aj.targetUrl = '';
        aj.sendString = '';
        aj.recvType = recvType ? recvType : 'XML';
        aj.resultHandle = null;

        aj.createXMLHttpRequest = function() {
                var request = false;
                if(window.XMLHttpRequest) {
                        request = new XMLHttpRequest();
                        if(request.overrideMimeType) {
                                request.overrideMimeType('text/xml');
                        }
                } else if(window.ActiveXObject) {
                        var versions = ['Microsoft.XMLHTTP', 'MSXML.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.7.0', 'Msxml2.XMLHTTP.6.0', 'Msxml2.XMLHTTP.5.0', 'Msxml2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP'];
                        for(var i=0; i<versions.length; i++) {
                                try {
                                        request = new ActiveXObject(versions[i]);
                                        if(request) {
                                                return request;
                                        }
                                } catch(e) {
                                        //alert(e.message);
                                }
                        }
                }
                return request;
        }

        aj.XMLHttpRequest = aj.createXMLHttpRequest();这一段是想尽 一切办法建立一个XMLHttpRequest对象,无论是什么浏览器都能通用了。调用的时候是一个函数Ajax,有两个传入函数recvType和 statusId,recvType是ajax返回值的接受类型,有HTML和XML两种类型,Dz一般用的是XML类型;statusID这个是用来指 示状态的p。

QUOTE:

CODE:

        aj.processHandle = function() {
                if(aj.statusId) {
                        aj.statusId.style.display = '';
                }
                if(aj.XMLHttpRequest.readyState == 1 && aj.statusId) {
                        aj.statusId.innerHTML = xml_http_building_link;
                } else if(aj.XMLHttpRequest.readyState == 2 && aj.statusId) {
                        aj.statusId.innerHTML = xml_http_sending;
                } else if(aj.XMLHttpRequest.readyState == 3 && aj.statusId) {
                        aj.statusId.innerHTML = xml_http_loading;
                } else if(aj.XMLHttpRequest.readyState == 4) {
                        if(aj.XMLHttpRequest.status == 200) {
                                for(k in Ajaxs) {
                                        if(Ajaxs[k] == aj.targetUrl) {
                                                Ajaxs[k] = null;
                                        }
                                }

                                if(aj.statusId) {
                                        aj.statusId.innerHTML = xml_http_data_in_processed;
                                        aj.statusId.style.display = 'none';
                                }
                                if(aj.recvType == 'HTML') {
                                        aj.resultHandle(aj.XMLHttpRequest.responseText, aj);
                                } else if(aj.recvType == 'XML') {
                                        aj.resultHandle(aj.XMLHttpRequest.responseXML.lastChild.firstChild.nodeValue, aj);
                                }
                        } else {
                                if(aj.statusId) {
                                        aj.statusId.innerHTML = xml_http_load_failed;
                                }
                        }
                }
        }Ajax实例化后的对象aj的processHandle方法,作用当然就是处理ajax请求过程的函数。
        具体作用有两点:
        第一点,那就是对statusId这个p进行ajax请求过程全程提示,在这段代码的前三分之一的样子就是做这个用的。
        注意在register.htm中有对过程的定义,以下的代码引用自./templates/default/register.htm

CODE:

                var profile_seccode_invalid = '{lang register_profile_seccode_invalid}';
                var profile_secanswer_invalid = '{lang register_profile_secqaa_invalid}';
                var profile_username_toolong = '{lang register_profile_username_toolong}';
                var profile_username_tooshort = '{lang register_profile_profile_username_tooshort}';
                var profile_username_illegal = '{lang register_profile_username_illegal}';
                var profile_passwd_illegal = '{lang register_profile_passwd_illegal}';
                var profile_passwd_notmatch = '{lang register_profile_passwd_notmatch}';
                var profile_email_illegal = '{lang register_profile_email_illegal}';
                var profile_email_invalid = '{lang register_profile_email_invalid}';
                var profile_email_censor = '{lang register_profile_email_censor}';
                var profile_email_msn = '{lang register_profile_email_msn}';
                var doublee = parseInt('$doublee');
                var lastseccode = lastsecanswer = lastusername = lastpassword = lastemail = '';
                var xml_http_building_link = '{lang xml_http_building_link}';
                var xml_http_sending = '{lang xml_http_sending}';
                var xml_http_loading = '{lang xml_http_loading}';
                var xml_http_load_failed = '{lang xml_http_load_failed}';
                var xml_http_data_in_processed = '{lang xml_http_data_in_processed}';
        这个便是statusId具体中要提示的文字了,之所以要这样写当然是为了方便多语言。
                
        第二点是最重要的,当XMLHttpRequest.status=200的时候,那么就表示请求成功并返回了东西,这个时候就用 resultHandle这个函数对返回的东西进行处理,可以看到还是分为HTML和XML两种情况分别调用不同的方法,一个是responsText一 个是responseXML。

QUOTE:

CODE:

        aj.get = function(targetUrl, resultHandle) {
                if(in_array(targetUrl, Ajaxs)) {
                        return false;
                } else {
                        arraypush(Ajaxs, targetUrl);
                }
                aj.targetUrl = targetUrl;
                aj.XMLHttpRequest.onreadystatechange = aj.processHandle;
                aj.resultHandle = resultHandle;
                if(window.XMLHttpRequest) {
                        aj.XMLHttpRequest.open('GET', aj.targetUrl);
                        aj.XMLHttpRequest.send(null);
                } else {
                        aj.XMLHttpRequest.open("GET", targetUrl, true);
                        aj.XMLHttpRequest.send();
                }
        }

        aj.post = function(targetUrl, sendString, resultHandle) {
                if(in_array(targetUrl, Ajaxs)) {
                        return false;
                } else {
                        arraypush(Ajaxs, targetUrl);
                }
                aj.targetUrl = targetUrl;
                aj.sendString = sendString;
                aj.XMLHttpRequest.onreadystatechange = aj.processHandle;
                aj.resultHandle = resultHandle;
                aj.XMLHttpRequest.open('POST', targetUrl);
                aj.XMLHttpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
                aj.XMLHttpRequest.send(aj.sendString);
        }
        return aj;
}这里是ajax的两个方法,一个是get,用来提交get数据的,比方说discuz对于注册的时候ajax的表情显示就是get方法;另外一个是post,用来提交post数据。
targetUrl, resultHandle这两个参数分别是要请求的地址和处理函数。

 

 笨才 发布于2007-05-28 12:23:19

有了这些理论的基础就可以分析一下ajax的具体实现了,下面就以注册过程中的检查用户名在数据库是不是存在并给用户提示这样一个ajax过程进行全程分析。
用到如下的几个文件:

QUOTE:

./register.php
./ajax.php
./include/javascript/common.js
./include/javascript/ajax.js
./template/default/register.htm


其实register.php这个文件没什么多大的关系,不过register.htm模板是通过它解析过来的,所以就提出来了。

大家都知道在注册过程中是在输完用户名并输入密码的时候才会触发事件检查是不是用户存在,所以很明显,这个是input的onBlur事件。
好了,现在看看./templates/default/register.htm这个文件。

QUOTE:

CODE:

<tr>
        <td class="altbg1" width="21%"><span class="bold">{lang username}</span></td>
        <td class="altbg2"><p class="input"><input type="text" name="username" size="25" maxlength="15" id="username" onBlur="checkusername()"></p><p id="checkusername"></p>
        </td>
        </tr>很简单的HTML代码,注意看到 onBlur="checkusername()"这里,继续往下深入,看看这个函数是做什么的。

CODE:

        function checkusername() {
                var username = trim($('username').value);
                if(username == lastusername) {
                        return;
                } else {
                        lastusername = username;
                }
                var cu = $('checkusername');
                var unlen = username.replace(/[^/x00-/xff]/g, "**").length;

                if(unlen < 3 || unlen > 15) {
                        warning(cu, unlen < 3 ? profile_username_tooshort : profile_username_toolong);
                        return;
                }
                ajaxresponse('checkusername', 'action=checkusername&username=' + username);
        }第一行是得到username的值,也就是我们输入的,lastusername应该是我们在返回的时候不会让用户名消失用的,应该是写入cookie或者是其他。
接下来判断经过了替换的用户名是不是大于15或者小于3,是的话直接调用warning这个函数(稍后讲这样一个函数)并返回,不是的话就调用ajaxresponse函数发出ajax请求,看看ajaxresponse这个函数就是关键所在了。

CODE:

        function ajaxresponse(objname, data) {
                var x = new Ajax('XML', objname);
                x.get('ajax.php?inajax=1&' + data, function(s){
                        var obj = $(objname);
                        if(s == 'succeed') {
                                obj.style.display = '';
                                obj.innerHTML = '<img src="{IMGDIR}/check_right.gif" width="13" height="13">';
                                obj.className = "warning";
                        } else {
                                warning(obj, s);
                        }
                });
        }ajaxresponse 函数来了,这个作用就是实例化一个ajax对象,注意到我们先前说的recvType,就是第一个传入参数,这里固定成了XML,Discuz喜欢用 XML(^^),然后发出请求,这里全部用的是get(第二行),地址是ajax.php?inajax=1&加上传入的参数,所以结合上面就变 成:ajax.php?inajax=1&action=checkusername&username=用户名,构造出来了一个 URL,(大家可以自己测试一下看看返回的是什么东东,通过http://你的URL/ajax.php?inajax=1&action= checkusername&username=用户名)通过XMLHttpRequest发出去,注意那个处理函数:function(s), 实际这个函数作为参数已经写出来了,如果最后返回的是succed,那么就在obj这个层里(在这个例子中对应id='checkusername'这个 层)显示一个带勾的图,否则的话还是调用warning这个函数。下面就分析这个函数。

CODE:

        function warning(obj, msg) {
                if((ton = obj.id.substr(5, obj.id.length)) != 'password2') {
                        $(ton).select();
                }
                obj.style.display = '';
                obj.innerHTML = '<img src="{IMGDIR}/check_error.gif" width="13" height="13">   ' + msg;
                obj.className = "warning";
        }warning函数前面两次提到,其实这个函数很简单,实现的作用就是在obj这个层里打一个叉,然后写上错误的原因,把obj这个层的CSS class设置成为warning。当然,最开始也验证了一下两次密码是否一致,这里就不说了。


接下来当然是要分析这个ajax.php是怎么一回事,它做了哪些使function(s)中能返回我们要的东西。由于只分析检查用户名这一个部分,我这里就只分析action=checkuser这一部分了。

QUOTE:

CODE:

elseif($action == 'checkusername') {

                $username = trim($username);

                $guestexp = '/xA1/xA1|^Guest|^/xD3/xCE/xBF/xCD|/xB9/x43/xAB/xC8';
                $censorexp = '/^('.str_replace(array('//*', "/r/n", ' '), array('.*', '|', ''), preg_quote(($censoruser = trim($censoruser)), '/')).')$/i';
                if(preg_match("/^/s*$|^c://con//con$|[%,/*/"/s/t/</>/&]|$guestexp/is", $username) || ($censoruser && @preg_match($censorexp, $username))) {
                        showmessage('profile_username_illegal');
                }

                $query = $db->query("SELECT uid FROM {$tablepre}members WHERE username='$username'");
                $username = dhtmlspecialchars(stripslashes($username));

                if($db->num_rows($query)) {
                        showmessage('register_check_found');
                }这里可以看到是标准的php判断了,有点点php基础就能看懂了,基本上的功能就是判断一个用户是不是在后台设置的禁用用户名中。
是的话就showmessage不合法(注:这里的showmessage不是我们理解的那个跳转,而是一个xml文档,为什么会这样我等会会介绍)
然后就从数据库找是不是有这样一个用户,如果是的话就showmessage 发现了已注册的用户名,不是话就都跳过,直接到最后的:

CODE:

showmessage('succeed');注意当所有的判断都成功的话就说明合法了,会调用showmessage来显示一个succeed。

QUOTE:

最后说一下为什么这里的showmessage不是我们理解的那个跳转了。

注意在register.htm中的ajaxresponse函数有这样一句:x.get('ajax.php?inajax=1&' + data, function(s){
对了,inajax=1,就是这么一个参数,showmessage就天差万别了。如果你不记得showmessage这个函数是什么样了,请参考:
http://www.discuz.net/viewthread.php?tid=612195

http://www.discuz.net/viewthread.php?tid=612197,我在这里分析了一下。

 

转自:http://blog.csdn.net/freefis/article/details/1839545

需要做网站?需要网络推广?欢迎咨询客户经理 13272073477