1.3 class_core 初始化与全局变量
弄懂 Discuz! 启动时干了什么,以及
$_G里那些能直接用的值。
1.3.1 每个请求都从这里开始
站点根目录的 forum.php、home.php、portal.php、member.php 等入口脚本做的事都一样:先把内核拉起来,再跑自己的逻辑。第一步是引入 source/class/class_core.php。
可以理解成「开机自检」。它把 PHP 环境对齐、连接数据库、确认你是谁、你有没有管理权限、把你这个页面要用的设置值预先读出来。等它跑完,你的插件代码才开始执行——所以 $_G 里那些值不是你自己查出来的,是它替你备好的。
要单独写一个不经入口脚本、浏览器直接打开的插件 .php 文件,第一行就得自己拉内核:
require './source/class/class_core.php';
$discuz = C::app();
$discuz->cachelist = [];
$discuz->init();
这三行是 X5.0 的标准起手式,缺一个都会出问题。加载插件内子文件用 require_once appfile('yyy/zzz');,渲染模板用 template('tpl', 0, 'source/app/xxx/template');(X5 的 app 模板仅支持 .php 格式)。
1.3.2 class_core 做的九件事
按官方文档的描述,class_core.php 的职责可以拆成九块:
- 处理 PHP 与操作系统的环境兼容性;
- 定义常量
IN_DISCUZ = TRUE,防止程序被非法直接引用; - 定义
DISCUZ_ROOT(站点绝对路径); - 加载
source/function/function_core.php; - 按
config/config_global.php建立数据库连接; - 提供
DB::table('tablename')获取带前缀的完整表名; - 判断登录状态,给
$_G['uid']、$_G['username']、$_G['member']赋值; - 判断管理权限,给
$_G['adminid']、$_G['groupid']、$_G['group']赋值; - 预载入各模块的设置变量。
第 2 条值得留意。IN_DISCUZ 的作用是防呆:插件文件被人直接访问而没先拉内核时,靠这个常量可以自己拦掉。在插件入口加一句 defined('IN_DISCUZ') or die('Access Denied'); 能挡掉一批扫站工具。
1.3.3 $_G 是什么
$_G 是 Discuz! 的全局配置数组,相当于「PHP 自己的 $_GET 被扩展成了一个万能上下文袋」。它跟 $_GET、$_POST 同级,任何函数作用域里直接写 $_G['xxx'] 就能访问。
| 键 | 含义 |
|---|---|
$_G['uid'] |
当前登录用户 ID,未登录为 0 |
$_G['username'] |
当前登录用户名 |
$_G['member'] |
当前用户详细信息数组 |
$_G['adminid'] |
管理身份标识,0~3 |
$_G['groupid'] / $_G['group'] |
当前用户组 ID / 信息数组 |
$_G['config'] |
站点全局配置 |
$_G['style'] |
模板风格常量 |
$_G['setting'] |
各模块的设置变量 |
1.3.4 登录态三件套,最容易搞混
这是插件开发者每天都在用的三个值,区别必须记清:
| 值 | 内容 | 用途 |
|---|---|---|
$_G['uid'] |
用户 ID 数字,非 0 即已登录 | 判断登录、做数据关联 |
$_G['username'] |
用户名 | 插数据库用这个 |
$_G['member']['username'] |
原始用户名 | 页面显示用这个 |
$_G['member']['password'] |
MD5 后的密码 | 极少用到,别泄露 |
两个用户名并存是有历史原因的:$_G['username'] 长期被当作「可以直接拼进 SQL 的安全字符串」使用,$_G['member']['username'] 保留未处理原值供显示。从 X2.5 起,$_G['username'] 已不再做 addslashes 处理——按老习惯写代码,在 X5.0 上反而会出问题。
现在的正确做法是:不要把它当成已转义字符串直接拼 SQL。用参数化查询,或者改用 $_G['uid'] 做数据关联。
1.3.5 更多信息用 getuserprofile()
$_G['member'] 里只是常用字段,想拿更多资料(邮箱、主页、签名、扩展资料等)调用 getuserprofile():
$profile = getuserprofile($_G['uid']);
这个函数会去查用户主表与字段表,返回以字段名为键的数组,具体行为以你的版本为准。
1.3.6 管理身份:adminid 与用户组
$_G['adminid'] |
身份 |
|---|---|
| 0 | 普通用户 |
| 1 | 论坛管理员 |
| 2 | 超级版主 |
| 3 | 论坛版主 |
用户组信息在 $_G['groupid'] 和 $_G['group']。扩展项目模块上可以设置「使用等级」,但那只是界面过滤——程序内部必须自己再校验一次。正确写法是两层都做:模块设等级防误入,脚本里查 $_G['adminid'] 防绕过。
1.3.7 在插件里安全地取当前用户
把上面几条拼起来,就是插件里最常写的一段代码。放进 source/plugin/myplugin/myplugin.func.php,任何需要登录态的地方调它:
<?php
// source/plugin/myplugin/myplugin.func.php
class myplugin {
const ::identifier = 'myplugin'; // 标识符,便于跨文件引用
public static function current_user() {
global $_G, $discuz;
if (!$_G['uid']) {
// 未登录:走 Discuz! 标准提示
showmessage('请先登录', 'member.php?mod=logging&action=login');
}
return array(
'uid' => (int)$_G['uid'], // 一律转 int
'username' => $_G['member']['username'], // 显示用原始值
'groupid' => $_G['groupid'],
'adminid' => $_G['adminid'],
);
}
public static function is_moderator() {
global $_G;
return $_G['adminid'] > 0;
}
}
几个要点:
- 函数内部要访问
$_G,用global $_G, $discuz;声明。 uid一律(int)强转,来源可能是字符串。- 提示跳转用
showmessage(),别自己拼header()。 - 显示用户名取
$_G['member']['username']。
往数据库写用户信息时,自由输入(例如表单里的昵称)必须走 daddslashes():
$nickname = daddslashes(trim($_POST['nickname']));
DB::insert('myplugin_log', array(
'uid' => $_G['uid'],
'nickname' => $nickname,
'time' => TIMESTAMP,
));
DB::insert() 传数组时值不会自动转义,转义得自己做。这是插件开发里最常见的安全事故来源,新代码一律按上面这个模式写。
1.3.8 本节要点
- 独立入口文件先拉内核:
require './source/class/class_core.php';加$discuz = C::app();加$discuz->init(); $_G是全局上下文,需要局部作用域时global $_G, $discuz;。$_G['uid']判断登录,$_G['username']插库,$_G['member']['username']显示,$_G['member']['password']别碰。- X2.5 起
$_G['username']不再做 addslashes,不要依赖转义。 $_G['adminid']是 0~3 的管理身份,模块设了等级也不等于权限校验。- 表前缀走
DB::table(),自由输入走daddslashes()。
下一节:怎么在后台「设计新插件」,导出的 XML 清单每个节点是什么意思。
