Z-Blog 插件钩子入门:机制拆解与常用钩子速查
Z-Blog 钩子本质是注册回调的数组,没有优先级参数,要调顺序只能动插件加载顺序。本文按 plugin.xml → RegisterPlugin → Add_Filter_Plugin → 触发执行四步拆开链路,并按场景列出常挂的钩子,附页头注入与后台加菜单两段可直接抄的代码,最后记三个实测坑:ActivePlugin 里别查库、文章与页面类型挂错、反复 ALTER TABLE 会报错。
先说个真事。前两天我给主题加导航下拉,顺手在首页塞了个「技术片段」板块,发完文章刷新一看——「暂无记录」。
查了半小时才反应过来:板块里取文章的函数把条件写死成 log_CateID = 9,而我把文章发在了子分类 zblog 下。Z-Blog 的 GetList 不会自动带上子分类,这一点它跟 WordPress 一样,都不带。子分类的文章父分类里是查不到的,得自己拼。
那要改吗?当然要。但我不碰 zb_system/。这篇就说说为什么不碰,以及钩子这套东西到底是怎么跑起来的——不是查字典那种解释,是我自己顺了一遍之后的版本。
一、改核心文件爽三分钟,后面全是账
改 zb_system/ 下的文件最快,改完立刻生效,没有比这更爽的。问题是这笔账迟早要还:升级一次,你自己都不记得改过哪几行;哪天出 bug,排查范围直接从「我的插件」扩大到「整个核心」;再往前一步,你想把站迁走或者交给别人接手,对方打开一看满目疮痍。
所以我给自己定了条规矩:能在插件 include.php 里用 Add_Filter_Plugin() 解决的,一律不去动模板和核心。这条规矩救过我不少次,尤其是升级的时候。
二、钩子是怎么跑起来的
别看文档写得玄乎,拆开就四步:
plugin.xml
↓
include.php → RegisterPlugin('MyPlug', 'ActivePlugin_MyPlug')
↓(后台启用插件时执行一次)
ActivePlugin_MyPlug() → Add_Filter_Plugin('钩子名', '回调函数名')
↓(运行到该钩子时)
按注册顺序挨个调用
看懂这张图,下面三件事你就不会搞错了:
- 钩子本质就是数组。注册就是往数组里塞函数名,触发时
foreach一遍。所以先注册的先执行,没有优先级参数这回事。 - 想调顺序,只能动插件加载顺序。后台插件管理里拖,或者改
ZC_USING_PLUGIN_LIST。单个钩子想加权?没有。 - 别把 WordPress 的
add_action搬过来。Z-Blog 里叫Add_Filter_Plugin,参数是钩子名 + 可调用函数名。闭包能传,但我劝你别传,出问题的时候你连函数名都搜不到。
三、我常挂的钩子,按场景排
下面这些钩子名不是从文档里抄的,是直接解析本站 zb_system/function/c_system_plugin.php 拿到的,复制就能用。
| 我要干什么 | 挂哪个 | 备注 |
|---|---|---|
| 全局初始化、埋点、收尾 | Filter_Plugin_Zbp_PreLoadFilter_Plugin_Zbp_LoadFilter_Plugin_Zbp_Terminate | Load 里请务必跳过 zb_system 和 zb_users/plugin,不然把自己搞挂 |
| 注册自定义模板标签 | Filter_Plugin_Zbp_BuildTemplateFilter_Plugin_Zbp_MakeTemplatetags | 模板里想写 {mytag} 就靠它 |
| 前台页面注入 | Filter_Plugin_Index_BeginFilter_Plugin_ViewList_Begin_V2Filter_Plugin_ViewPost_BeginFilter_Plugin_ViewSearch_Begin | 首页、列表、文章、搜索,各挂各的 |
| 后台加菜单加样式 | Filter_Plugin_Admin_LeftMenuFilter_Plugin_Admin_TopMenuFilter_Plugin_Admin_Header | LeftMenu 传的是引用,往里塞就行 |
| 给编辑页加字段 | Filter_Plugin_Edit_BeginFilter_Plugin_Edit_Response | 实测 Response 用来输出表单 |
| 改查询结果、拦 SQL | Filter_Plugin_GetList_ResultFilter_Plugin_DbSql_Filter | 想做相关推荐可以在这里动手 |
| 登录审计 | Filter_Plugin_VerifyLogin_SucceedFilter_Plugin_VerifyLogin_Failed | 登录失败次数限制就挂这两个 |
表格里没有的,自己用 grep -n "Filter_Plugin_" c_system_plugin.php 翻一遍,比搜教程靠谱得多。
四、两个能直接抄的例子
例 1:全站页头塞一段 meta
function ActivePlugin_MyPlug() {
Add_Filter_Plugin('Filter_Plugin_Index_Begin', 'MyPlug_Index_Begin');
}
function MyPlug_Index_Begin() {
global $zbp;
$zbp->header .= '<meta name="x-myplug" content="1">';
}
这里有个字要注意:是 .=,不是 =。写成 = 的话主题自己加的 meta 会被你一句话抹掉,页面看着正常,其实一堆东西没了。我第一次写的时候就是这么干的。
例 2:后台左侧加一个菜单
function ActivePlugin_MyPlug() {
Add_Filter_Plugin('Filter_Plugin_Admin_LeftMenu', 'MyPlug_Admin_LeftMenu');
}
function MyPlug_Admin_LeftMenu(&$leftMenus) {
$leftMenus['nav_myplug'] = array(
'id' => 'myplug',
'name' => '我的插件',
'url' => $GLOBALS['zbp']->host . 'zb_users/plugin/MyPlug/main.php',
'target' => '_self',
);
}
参数是数组引用,注意那个 &。直接改数组,千万别 return——return 出去没人接,你会盯着后台刷新十遍以为缓存没清。
五、我踩过的三个坑
- 别在
ActivePlugin_里查数据库。这个函数在插件启用那一刻执行,此时连$zbp的数据库状态都不一定就绪。我在这里写过一次GetList,结果是白屏,连错误提示都没有,纯白。 - 文章和页面是两个东西。article 和 page 是不同的 Post 类型,钩子挂错的表现就是「文章页生效、单页不生效」,或者反过来。遇到这种灵异现象先看这里。
- 改表结构别偷懒。不要指望在
ActivePlugin_里无条件ALTER TABLE,第二次启用就报错。老老实实用SHOW COLUMNS判断字段在不在,再决定加不加。
六、下一步
第 2 篇进前台实战:首页、列表页、文章页、搜索页各自该挂哪个钩子,_V2 版本和老版参数差在哪,以及为什么混用会直接白屏。第 3 篇写后台(加菜单、加字段、加列表列),第 4 篇讲数据层和插件升级。
最后提醒一句:本文所有钩子名都取自本站核心文件的实际解析结果,Z-Blog 1.7 系列可以直接用。但版本升级后钩子增删是常事,抄之前最好在你自己站上 grep 一遍。