1.1 Discuz! 插件机制全景
搞清 Discuz! 在哪些地方留了接口,你的功能该插进哪个孔。
1.1.1 一个准确的比方:插座与插头
Discuz! 的内核像一套装修好的房子。工班装修时在墙上预留了一圈插座(钩子 Hook),在配电箱里留了空位(后台模块),还在弱电箱里留了几个带标签的接口(广告类、道具类、任务类扩展)。你后买的冰箱、微波炉、扫地机器人——也就是插件——只要把插头对准形状拧上去就能通电。
反过来,非要往墙上开新洞、改承重墙,那就是「改内核代码」:轻则升级冲突,重则整站白屏。
Discuz! X5.0 的插件机制,本质上就是一句话:功能写在你自己的目录里,内核只负责在你登记过的位置调用它。 官方反复强调不改核心文件,就是因为一旦改了,从 X5.0 升到下个小版本时这些改动得手工重合并一遍。
1.1.2 插件和模板:两个方向,别混
这是新手最容易搞混的一组概念,先划清楚:
| 维度 | 插件(Plugin) | 模板(Template / Style) |
|---|---|---|
| 扩展的是 | PHP 逻辑、数据处理 | 页面 HTML 结构与配色 |
| 代码放哪 | source/plugin/{标识}/ |
template/{标识}/ |
| 谁来加载 | 内核按模块定义调用类或脚本 | 页面渲染时解析 .htm / .php |
| 面向用户 | 开发者二次开发 | 站长换皮肤、调 DIY |
两者的连接点是钩子:模板里写 {hook/钩子名} 预留位置,插件在同名钩子上挂函数往里吐东西。准确的说法是——插件是 PHP 侧扩展,模板是展示侧扩展,两者靠钩子连接。
只做展示改版,用模板够了;要做查库、改数据、发通知这类逻辑,必须上插件。
1.1.3 官方推荐的实现流程
- 需求评估:解决什么问题,要不要建表,有没有性能或安全风险。
- 总体设计:定功能边界、页面形态、数据表结构、需要挂哪些钩子。
- 读文档并体验接口:先在后台把相关模块类型点一遍,看接口返回什么。
- 编写代码和模板:插件目录、类文件、模板文件、语言包。
- 导出打包:后台把配置导出成 XML 清单,方便分发安装。
- 根据反馈完善:正式环境跑一轮,按反馈迭代。
第 3 步最容易被跳过,结果是写完 300 行才发现钩子名写错了。花 10 分钟点一遍,收益远大于事后返工。
1.1.4 扩展点总表
扩展入口不止「后台插件」一个,按官方文档分类整理,选型时先查这张表:
| 扩展点 | 能做什么 | 典型场景 |
|---|---|---|
| 页面嵌入(钩子 Hook) | 在指定页面指定位置插入或拦截代码 | 帖子页顶部加提示、登录框旁加第三方图标 |
| 特殊主题 Hook | 只在某些主题类型触发 | 只对特定版块显示广告或公告 |
| 后台模块(程序链接) | 在主导航、插件菜单、顶部/底部/快捷/家园导航加入口 | 给插件加后台菜单 |
| 扩展项目 | 个人设置子页、站点帮助、我的帖子、门户管理、论坛管理、管理中心新增管理模块 | 插件自带的设置页 |
| 程序脚本(页面嵌入普通版/手机版) | 一个独立页面,普通版与手机版各一套 | 插件的活动页、榜单页 |
| 广告类 | 注册自定义广告位类型 | 自定义尺寸的联盟广告 |
| 道具类 | 注册一种新道具 | 积分商城的特殊道具 |
| 任务类 | 注册一种新任务 | 每日签到类任务 |
| 子文件接管(child) | 直接顶替系统某段逻辑 | 替换邮件发送、对象存储实现 |
| RESTful API | 用 XML 声明式暴露接口,支持 get / post |
给 App、小程序供数 |
| 模板变量(X5.0) | 模板自带后台配置页,不必再配插件 | 让站长自己调轮播数量 |
几个必须提前知道的坑:
- 模块能设「使用等级」(比如只给超级版主),但这不等于权限校验。程序内部必须自己再查一遍
$_G['adminid'],否则普通用户直接输 URL 就能进后台页。 - X3.1 起新增「管理中心-列表链接」(远程链接),启用/禁用脚本也补齐了。
- 子文件接管是 X5.0 最强、也最危险的扩展点,接之前先读清楚被接管文件原本做了什么。
- X5.0 的模板变量完全复刻了插件变量机制。需求只是「让站长调几个数」,做到这步就够。
1.1.5 钩子是怎么工作的
钩子分两层:一个是全局嵌入点类,所有脚本共享;一个是按脚本区分的类,只在特定入口脚本生效。类名有固定拼法:plugin_ 前缀 + 插件唯一标识符,mobileplugin_ 前缀是手机版,再拼嵌入点所在脚本名(CURSCRIPT,forum.php 就写 forum)。
<?php
class plugin_myplugin {
function global_HookId() {}
function common() {}
}
class plugin_myplugin_forum extends plugin_myplugin {
function HookId() {}
function HookId_output($value) {}
}
全局嵌入点类必须存在,否则页面嵌入这一项不可用。模板侧对应写法是 {hook/global_usernav_extra2} 这样的标签,插在源码的哪个位置,就决定它显示在哪。
常用方法:HookId() 在模块执行前跑;HookId_output($value) 在模块执行完、模板输出前跑,参数里有 template、message、values 三个键,可以改输出模板;HookId_message($value) 在 showmessage() 执行时触发。
1.1.6 从 URL 看懂插件入口
有些文件不需要在后台定义成模块,存在就能访问,入口是根目录的 plugin.php:
plugin.php?id=xxx:yyy
plugin.php?id=xxx
第一行访问 source/plugin/xxx/yyy.inc.php;xxx 与 yyy 同名时用第二行简写。这是 X3 内核的规则,plugin.php 作为 CURSCRIPT 时类名里写 plugin。
方便,但意味着任何 .inc.php 都是潜在入口,文件里的权限判断不能省。
1.1.7 X5.0 的前提
X5.0 用全新的 MitFrame 框架体系:目录按 APP 重新归类(source/app/ 承接原 source/module 的职责,include 的位置被 child/ 取代),不再兼容 X3.5 及更早版本。从老版本升上来的,先别急着上新功能,把现有插件在 X5.0 上跑一遍。
环境也确认一下:开源版要求 PHP 8.0+,数据库 MySQL 5.7~9 或 MariaDB 10.2+。X5.1 企飞版要求 PHP 8.2+,闭源授权,接口与开源版不同。
1.1.8 本节要点
- 插件的本质是「在预留接口上挂代码」,不碰内核文件。
- 扩展点有十来个。判断标准很简单:要不要建表、要不要改数据、要不要发通知——要就上插件,不要就用模板变量。
- 六步流程里「读文档并体验接口」别跳。
- X5.0 不兼容 X3.5 及更早版本,老插件需要改造。
下一节开始落地:先把目录建起来。
