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 的职责可以拆成九块:

  1. 处理 PHP 与操作系统的环境兼容性;
  2. 定义常量 IN_DISCUZ = TRUE,防止程序被非法直接引用;
  3. 定义 DISCUZ_ROOT(站点绝对路径);
  4. 加载 source/function/function_core.php;
  5. 按 config/config_global.php 建立数据库连接;
  6. 提供 DB::table('tablename') 获取带前缀的完整表名;
  7. 判断登录状态,给 $_G['uid']、$_G['username']、$_G['member'] 赋值;
  8. 判断管理权限,给 $_G['adminid']、$_G['groupid']、$_G['group'] 赋值;
  9. 预载入各模块的设置变量。

第 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 清单每个节点是什么意思。