Home > Article > Backend Development > [zz]discuzX1核心资料class_core.php分析
[zz]discuzX1核心文件class_core.php分析
/*Discuz!X 核心文件class_core.php分析
2010-08-24 10:20 在研究DISCUZ!X核心文件class_core.php时作的DISCUZ!X产品相较之前版本的底层机制上的变化分析及源码部分注释。 ___________________________________________
我今天花了1个多小时,阅读了下class_core.php,DB数据库和cron任务计划没看。总体感觉,比起dz72来说,各项机制和体系都有所增强,变化相当大。 【1】最明显的一点是,核心体系基本都用类进行封装了,架构清晰,使用起来相对简易,基本的架构类似Ucenter,但略有增强。 【2】安全机制进一步加强,特别是GLOBALS超级全局变量的注销,及各变量的初始化,让人印象深刻。 【3】原来的GPC和session机制,大体变化不算很大,但进行了一些优化和增强,考虑得更全面了。不过新版清晰与普通变量进行了区隔,如get、 post用 gp_,而cookie用$cookie数组,而session用$session。 【4】功能上,基本上原有的客户端ip、机器人判断、转义、gzhandler、对xss跨站攻击的基本防御、缓存丢失判断等基本上差别不算太大。 【5】增加了memory内存读写引擎,支持memcache、xcache,eAccelerator。 【6】在原有session机制处理逻辑基础上,精简了一部分实现,重新自定义并增强了SESSION机制,代码的写法与结构与memory写法类似。但具体效果有待验证。
附随笔作的注释大家将就看吧 */
<?php /** * [Discuz!] (C)2001-2099 Comsenz Inc. * This is NOT a freeware, use is subject to license terms * * $Id: class_core.php 6914 2010-03-26 12:52:36Z cnteacher $ */ ///TODO 是将要完成的功能,包括禁止ip和禁止访问 //TODO 禁止ip //TODO 禁止访问 ///和DZ72一样,确保所有文件需要先加载核心文件 define('IN_DISCUZ', true); /** * Discuz 核心引擎 * 其他处理代码当中用到的变量不要在本核心 new 之前设置, 否则会自动清空 * */ class discuz_core { // 数据库存储引擎 var $db = null; // 内存缓冲object var $mem = null; // 会话 object var $session = null; // 程序配置 var $config = array(); // $_G 数组的映射 var $var = array(); // 加载缓存的数组 var $cachelist = array(); // 是否初始化 var $init_setting = true; //设置 var $init_user = true;//用户 var $init_session = true;//会话 var $init_cron = true;//任务计划 var $init_misc = true;//其他功能 var $init_memory = true;//内存 // 是否已经初始化 var $initated = false; var $superglobal = array( 'GLOBALS' => 1, '_GET' = 1, '_POST' = 1, '_REQUEST' = 1, '_COOKIE' = 1, '_SERVER' = 1, '_ENV' = 1, '_FILES' = 1, ); function &instance() { static $object; if(empty($object)) { $object = new discuz_core(); } return $object; } function discuz_core() { $this->_init_env(); $this->_init_config(); $this->_init_input(); $this->_init_output(); } function init() { if(!$this->initated) { $this->_init_db(); $this->_init_memory(); $this->_init_user(); $this->_init_session(); $this->_init_setting(); $this->_init_cron(); $this->_init_misc(); } $this->initated = true; } function _init_env() { error_reporting(E_ALL ^ E_NOTICE); // error_reporting(E_ALL); ///php 5.3前则关闭魔法引号匹配(自动转义) if(phpversion() error('function_core.php is missing'); } //判断浏览器是否是蜘蛛 define('IS_ROBOT', checkrobot()); //清理全局变量 ///全清理了,真是彻底把所有变量都从内存中注销了 foreach ($GLOBALS as $key = $value) { if (!isset($this->superglobal[$key])) { $GLOBALS[$key] = null; unset($GLOBALS[$key]); } } // 配置全局变量 ///和上一步结合,只留下自己需要的变量,并初始化。 ///这么做够狠,只要稍微小心点,就不会出现因为变量未初始化而出现的安全问题 global $_G; $_G = array( //公用全局定义 'uid' = 0, 'username' = '', 'adminid' = 0, 'groupid' = 1, 'sid' = '', 'formhash' = '', 'timestamp' = TIMESTAMP, 'starttime' = dmicrotime(), 'clientip' = $this->_get_client_ip(), 'referer' = '', 'charset' = '', 'gzipcompress' = '', 'authkey' = '', 'timenow' = array(), 'PHP_SELF' = '', 'siteurl' = '', //公用全局数组定义 'config' = array(), 'setting' = array(), 'member' = array(), 'group' = array(), 'cookie' = array(), 'style' = array(), 'cache' = array(), 'session' = array(), 'lang' = array(), 'my_app' = array(),//默认应用 'my_userapp' = array(),//用户自添加应用 //论坛全局定义 'fid' = 0, 'tid' = 0, 'forum' = array(), 'rssauth' = '', //uch 全局定义 'home' = array(), 'space' = array(), //portal 全局定义 'block' = array(), 'article' = array(), //Action 'action' = array( 'action' = APPTYPEID, 'fid' = 0, 'tid' = 0, ) ); //相对主目录的相对地址及文件名 $_G['PHP_SELF'] = htmlspecialchars($_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME']); //基本脚本名,每个功能脚本首页前都会定义 //比如forum.php,则定义CURSCRIPT为forum,而forum_forumdisplay.php则不定义,因为属于forum $_G['basescript'] = CURSCRIPT; //站点网址 $_G['siteurl'] = htmlspecialchars('http://'.$_SERVER['HTTP_HOST'].preg_replace("/\/+(api)?\/*$/i", '', substr($_G['PHP_SELF'], 0, strrpos($_G['PHP_SELF'], '/'))).'/'); ///$_G的映射,也即超级全局变量 $this->var = & $_G; } function _init_input() { //note 禁止对全局变量注入 ///和dz72类似,禁止GLOBALS=xxx的方式注入 if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS'])) { error('request_tainting'); } if(!empty($_GET['rewrite'])) { $query_string = '?mod='; $param = explode('-', $_GET['rewrite']); $query_string .= $_GET['mod'] = $param[0]; array_shift($param); $paramc = count($param); for($i = 0;$i config['cookie']['cookiepre']); foreach($_COOKIE as $key = $val) { if(substr($key, 0, $prelength) == $this->config['cookie']['cookiepre']) { $this->var['cookie'][substr($key, $prelength)] = $val; } } $_GET['diy'] = empty($_GET['diy']) ? '' : $_GET['diy']; ///$_GET和$_POST转成与索引同名加"gp_"前缀的变量 ///如$_GET['username']直接用$gp_username来访问 foreach(array_merge($_POST, $_GET) as $k = $v) { $this->var['gp_'.$k] = $v; } ///根据$_GET['mod']来确定m的值,$this->var为全局数组,gp_为上个语句的附加前缀 $this->var['mod'] = empty($this->var['gp_mod']) ? '' : htmlspecialchars($this->var['gp_mod']); ///如果使用ajax,再判断是post传值或get和xmlhttprequest同时有效 $this->var['inajax'] = empty($this->var['gp_inajax']) ? 0 : ($_SERVER['REQUEST_METHOD'] == 'GET' && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' || $_SERVER['REQUEST_METHOD'] == 'POST' ? 1 : 0); ///当前页码 $this->var['page'] = empty($this->var['gp_page']) ? 1 : max(1, intval($this->var['gp_page'])); ///确定cookie中的sid值 $this->var['sid'] = $this->var['cookie']['sid'] = isset($this->var['cookie']['sid']) ? htmlspecialchars($this->var['cookie']['sid']) : ''; } ///初始化设置 function _init_config() { ///加载设置文件 $_config = array(); @include DISCUZ_ROOT.'./config/config_global.php'; if(empty($_config)) { error('config_notfound'); } ///确定密钥,如果值为空,则密钥默认为cookie前缀与数据库名拼接的md5值,否则为配置文件中的值 ///authkey密钥是sid等参数加解密的重要参数 $_config['security']['authkey'] = empty($config['security']['authkey']) ? md5($_config['cookie']['cookiepre'].$_config['db'][1]['dbname']) : ($config['security']['authkey']); $this->config = & $_config; ///Discuz的调试 if(empty($this->config['debug']) || !file_exists(libfile('function/debug'))) { define('DISCUZ_DEBUG', false); } elseif($this->config['debug'] === 1 || $this->config['debug'] === 2 || !empty($_REQUEST['debug']) && $_REQUEST['debug'] === $this->config['debug']) { define('DISCUZ_DEBUG', true); if($this->config['debug'] == 2) { error_reporting(E_ALL); } } $GLOBALS['_G']['config'] = & $this->config; ///以浏览器版本为参考,进行密钥的二次md5加密 $GLOBALS['_G']['authkey'] = md5($this->config['security']['authkey'].$_SERVER['HTTP_USER_AGENT']); } function _init_output() { ///如果设置中打开xss跨站脚本的防御模式,且网址中存在"config['security']['urlxssdefend'] && !empty($_SERVER['REQUEST_URI'])) { $temp = urldecode($_SERVER['REQUEST_URI']); if(strpos($temp, 'config['output']['gzip'] && EXT_OBGZIP) { ob_start('ob_gzhandler'); setglobal('gzipcompress', true); } else { ob_start(); setglobal('gzipcompress', false); } ///确定HTML页面编码,及其他编码 if($this->config['output']['forceheader']) { @header('Content-Type: text/html; charset='.$this->config['output']['charset']); } setglobal('charset', $this->config['output']['charset']); define('CHARSET', $this->config['output']['charset']); } ///拒绝机器人访问 function reject_robot() { if(IS_ROBOT) { exit(header("HTTP/1.1 403 Forbidden")); } } ///获取客户端ip, function _get_client_ip() { $clientip = ''; ///环境变量客户端ip有值且字符长度大于unknown,则说明该变量有效,确定为客户端ip if(getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) { $clientip = getenv('HTTP_CLIENT_IP'); ///否则取当前浏览用户的网关ip地址 } elseif(getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) { $clientip = getenv('HTTP_X_FORWARDED_FOR'); ///用户计算机的ip地址 } elseif(getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) { $clientip = getenv('REMOTE_ADDR'); } elseif(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) { $clientip = $_SERVER['REMOTE_ADDR']; } ///判断是否是数字与点组成的7-15位字符 preg_match("/[\d\.]{7,15}/", $clientip, $clientipmatches); $clientip = $clientipmatches[0] ? $clientipmatches[0] : 'unknown'; return $clientip; } function _init_db() { ///生成数据库对象 $this->db = & DB::object(); ///加载设置文件并连接数据库 $this->db->set_config($this->config['db']); $this->db->connect(); } function _init_session() { $this->session = new discuz_session(); if($this->init_session) { ///传入sid,客户端ip与uid作为session判断机制,分新老用户,老用户则查session表,否则创建 $this->session->init($this->var['cookie']['sid'], $this->var['clientip'], $this->var['uid']); $this->var['sid'] = $this->session->sid; $this->var['session'] = $this->session->var; if($this->var['sid'] != $this->var['cookie']['sid']) { dsetcookie('sid', $this->var['sid'], 86400); } // 首次登陆更新最后访问时间,每隔 10 分钟更新用户最后动作时间 if($this->var['uid'] && ($this->session->isnew || ($this->session->get('lastactivity') + 600) session->set('lastactivity', TIMESTAMP); $update = array('lastip' = $this->var['clientip'], 'lastactivity' = TIMESTAMP); if($this->session->isnew) { $update['lastvisit'] = TIMESTAMP; } 更新会员状态 DB::update('common_member_status', $update, "uid='".$this->var['uid']."'"); } } } function _init_user() { if($this->init_user) { if($auth = getglobal('auth', 'cookie')) { $auth = daddslashes(explode("\t", authcode($auth, 'DECODE'))); } list($discuz_pw, $discuz_uid) = empty($auth) || count($auth) var['member'] = $user; } else { $user = array(); $this->_init_guest(); } $this->cachelist[] = 'usergroup_'.$this->var['member']['groupid']; if($user && $user['adminid'] 0 && $user['groupid'] != $user['adminid']) { $this->cachelist[] = 'admingroup_'.$this->var['member']['adminid']; } } else { $this->_init_guest(); } if(empty($this->var['cookie']['lastvisit'])) { $this->var['member']['lastvisit'] = TIMESTAMP - 3600; dsetcookie('lastvisit', TIMESTAMP - 3600, 86400 * 30); } else { $this->var['member']['lastvisit'] = empty($this->var['cookie']['lastvisit']); } setglobal('uid', getglobal('uid', 'member')); setglobal('username', addslashes(getglobal('username', 'member'))); setglobal('adminid', getglobal('adminid', 'member')); setglobal('groupid', getglobal('groupid', 'member')); } function _init_guest() { setglobal('member', array( 'uid' = 0, 'username' = '', 'groupid' = 7, 'credits' = 0, 'timeoffset' = 9999)); } function _init_cron() { if($this->init_cron && $this->init_setting) { if($this->var['cache']['cronnextrun'] init_misc) { return false; } // 调入核心语言包 lang('core'); //处理全局时区设置 if($this->init_setting && $this->init_user) { if(!isset($this->var['member']['timeoffset']) || $this->var['member']['timeoffset'] == 9999 || $this->var['member']['timeoffset'] === '') { $this->var['member']['timeoffset'] = $this->var['setting']['timeoffset']; } } $timeoffset = $this->init_setting ? $this->var['member']['timeoffset'] : $this->var['setting']['timeoffset']; $this->var['timenow'] = array( 'time' = dgmdate(TIMESTAMP), 'offset' = $timeoffset = 0 ? ($timeoffset == 0 ? '' : '+'.$timeoffset) : $timeoffset ); $this->timezone_set($timeoffset); $this->var['formhash'] = formhash(); define('FORMHASH', $this->var['formhash']); // 定义风格常量 if(is_array($this->var['style'])) { foreach ($this->var['style'] as $key = $val) { $key = strtoupper($key); if(!defined($key) && !is_array($val)) { define($key, $val); } } } //论坛开关检查 if($this->var['setting']['**losed'] && !(in_array($this->var['mod'], array('logging', 'seccode')) || getglobal('adminid', 'member') == 1)) { $closedreason = DB::result_first("SELECT svalue FROM ".DB::table('common_setting')." WHERE skey='closedreason'"); showmessage($closedreason ? $closedreason : 'board_closed', NULL, array(), array('login' = 1)); } $this->var['tpp'] = $this->var['setting']['topicperpage'] ? intval($this->var['setting']['topicperpage']) : 20; $this->var['ppp'] = $this->var['setting']['postperpage'] ? intval($this->var['setting']['postperpage']) : 10; if($this->var['setting']['nocacheheaders']) { @header("Expires: -1"); @header("Cache-Control: no-store, private, post-check=0, pre-check=0, max-age=0", FALSE); @header("Pragma: no-cache"); } $lastact = TIMESTAMP."\t".htmlspecialchars(basename($this->var['PHP_SELF']))."\t".htmlspecialchars($this->var['mod']); dsetcookie('lastact', $lastact, 86400); } function _init_setting() { if($this->init_setting) { if(empty($this->var['setting'])) { $this->cachelist[] = 'setting'; } if(empty($this->var['style'])) { $this->cachelist[] = 'style_default'; } if(!isset($this->var['cache']['cronnextrun'])) { $this->cachelist[] = 'cronnextrun'; } } !empty($this->cachelist) && loadcache($this->cachelist); if(!is_array($this->var['setting'])) { $this->var['setting'] = array(); } if($this->var['member'] && $this->var['member']['adminid'] 0 && $this->var['member']['groupid'] != $this->var['member']['adminid'] && !empty($this->var['cache']['admingroup_'.$this->var['member']['adminid']])) { $this->var['group'] = array_merge($this->var['group'], $this->var['cache']['admingroup_'.$this->var['member']['adminid']]); } } function _init_memory() { $this->mem = new discuz_memory(); if($this->init_memory) { $this->mem->init($this->config['memory']); } $this->var['memory'] = $this->mem->type; } function timezone_set($timeoffset = 0) { if(function_exists('date_default_timezone_set')) { @date_default_timezone_set('Etc/GMT'.($timeoffset 0 ? '-' : '+').(abs($timeoffset))); } } function error($msg, $halt = true) { $this->error_log($msg); echo $msg; $halt && exit(); } function error_log($message) { $time = date("Y-m-d H:i:s", TIMESTAMP); $file = DISCUZ_ROOT.'./data/log/errorlog_'.date("Ym").'.txt'; $message = "\n#{$time}:\t".str_replace(array("\t", "\r", "\n"), " ", $message); error_log($message, 3, $file); } } /** * Discuz MySQL 类的支持 * */ class db_mysql { var $tablepre; var $version = ''; var $querynum = 0; var $curlink; var $link = array(); var $config = array(); var $sqldebug = array(); function db_mysql($config = array()) { if(!empty($config)) { $this->set_config($config); } } function set_config($config) { $this->config = &$config; $this->tablepre = $config['1']['tablepre']; } function connect() { if(empty($this->config) || empty($this->config[1])) { $this->halt('notfound_config'); } foreach ($this->config as $id = $config) { $this->link[$id] = $this->_dbconnect( $config['dbhost'], $config['dbuser'], $config['dbpw'], $config['dbcharset'], $config['dbname'], $config['pconnect'] ); } $this->curlink = $this->link[1]; } function _dbconnect($dbhost, $dbuser, $dbpw, $dbcharset, $dbname, $pconnect) { $link = null; $func = empty($pconnect) ? 'mysql_connect' : 'mysql_pconnect'; if(!$link = @$func($dbhost, $dbuser, $dbpw, 1)) { $this->halt('notconnect'); } else { $this->curlink = $link; if($this->version() '4.1') { $serverset = $dbcharset ? 'character_set_connection='.$dbcharset.', character_set_results='.$dbcharset.', character_set_client=binary' : ''; $serverset .= $this->version() '5.0.1' ? ((empty($serverset) ? '' : ',').'sql_mode=\'\'') : ''; $serverset && mysql_query("SET $serverset", $link); } $dbname && @mysql_select_db($dbname, $link); } return $link; } function table_name($tablename) { return $this->tablepre.$tablename; } function select_db($dbname) { return mysql_select_db($dbname, $this->curlink); } function fetch_array($query, $result_type = MYSQL_ASSOC) { return mysql_fetch_array($query, $result_type); } function fetch_first($sql) { return $this->fetch_array($this->query($sql)); } function result_first($sql) { return $this->result($this->query($sql), 0); } function query($sql, $type = '') { if(defined('DISCUZ_DEBUG') && DISCUZ_DEBUG) { $starttime = dmicrotime(); } $func = $type == 'UNBUFFERED' && @function_exists('mysql_unbuffered_query') ? 'mysql_unbuffered_query' : 'mysql_query'; if(!($query = $func($sql, $this->curlink))) { if(in_array($this->errno(), array(2006, 2013)) && substr($type, 0, 5) != 'RETRY') { $this->connect(); return $this->query($sql, 'RETRY'.$type); } if($type != 'SILENT' && substr($type, 5) != 'SILENT') { $this->halt('query_error', $sql); } } if(defined('DISCUZ_DEBUG') && DISCUZ_DEBUG) { $this->sqldebug[] = array($sql, number_format((dmicrotime() - $starttime), 6), debug_backtrace()); } $this->querynum++; return $query; } function affected_rows() { return mysql_affected_rows($this->curlink); } function error() { return (($this->curlink) ? mysql_error($this->curlink) : mysql_error()); } function errno() { return intval(($this->curlink) ? mysql_errno($this->curlink) : mysql_errno()); } function result($query, $row = 0) { $query = @mysql_result($query, $row); return $query; } function num_rows($query) { $query = mysql_num_rows($query); return $query; } function num_fields($query) { return mysql_num_fields($query); } function free_result($query) { return mysql_free_result($query); } function insert_id() { return ($id = mysql_insert_id($this->curlink)) = 0 ? $id : $this->result($this->query("SELECT last_insert_id()"), 0); } function fetch_row($query) { $query = mysql_fetch_row($query); return $query; } function fetch_fields($query) { return mysql_fetch_field($query); } function version() { if(empty($this->version)) { $this->version = mysql_get_server_info($this->curlink); } return $this->version; } function close() { return mysql_close($this->curlink); } function halt($message = '', $sql = '') { global $_G; $dberror = $this->error(); $dberrno = $this->errno(); $phperror = '
File | Line | Function |
$error[file] | $error[line] | $error[class]$error[type]$error[function]() |
?