2.4 钩子实战:帖子解析、删除拦截与消息改写
三个能直接抄走的例子,以及怎么查到自己要的钩子。
三个案例都放在 source/plugin/hello/hello.class.php 里,前提是 2.3 讲的全局嵌入点类已经存在。
案例一:帖子内容解析时插入内容
discuzcode() 是渲染帖子正文的函数——你看到的正文里,UBB 标签、表情、图片最终都经过它。要在解析时动手脚,就实现同名方法。
设计意图:不改数据库,只在输出时加工。关掉插件内容立刻恢复原样,出问题没有脏数据。
<?php
class plugin_hello {
// 帖子内容解析时调用
function discuzcode($value) {
global $_G, $discuz;
// $_G['discuzcodemessage'] 是当前待解析的字串
$message = $_G['discuzcodemessage'];
// $value 含 param 与 caller,据此区分调用场景
if ($value['caller'] !== 'post') {
return $value;
}
// 只对含指定标记的帖子生效,避免污染全站
if (strpos($message, '[hello]') === false) {
return $value;
}
$message = str_replace(
'[hello]',
'<div class="hello-box">你好,这是一条插件插入的内容。</div>',
$message
);
// 改完写回,否则解析器拿到的还是原串
$_G['discuzcodemessage'] = $message;
return $value;
}
}
关键三点:用 $_G['discuzcodemessage'] 取待解析串、用 $value['caller'] 区分场景、改完必须写回同一个键。这个方法返回 $value 而非字符串,改的是全局里的字串。
案例二:删除前后做拦截与统计
deletethread() 和 deletepost() 是删除类钩子,有个特别之处:一次操作会被调用 2 次,$value['step'] 区分阶段。
step |
时机 | 用途 |
|---|---|---|
check |
删除前 | 校验权限、记录待删清单 |
delete |
删除后 | 写日志、清理关联数据 |
待处理的 ID 数组放在 $_G['deletethreadtids'](主题)和 $_G['deletepostids'](帖子)里。
<?php
class plugin_hello {
// 主题删除前后各调一次
function deletethread($value) {
global $_G;
$tids = isset($_G['deletethreadtids']) ? $_G['deletethreadtids'] : array();
if (!$tids) {
return $value;
}
if ($value['step'] == 'check') {
// 删除前:记录清单
foreach ($tids as $tid) {
C::t('myplugin_log')->insert(array(
'type' => 'delete_thread',
'targetid' => $tid,
'operator' => $_G['uid'],
'dateline' => TIMESTAMP,
));
}
} else {
// 删除后:清理自己的关联数据
DB::delete('myplugin_thread_setting', DB::field('tid', array($tids)));
}
return $value;
}
}
三个要点:先判 step 再动手,否则同样逻辑会跑两遍;TID 数组用 DB::field() 转成 SQL 片段,别自己拼引号;关联表清理放 delete 阶段,跟系统操作保持一致。deletepost() 写法一样,只是数组换成 $_G['deletepostids']。
真要做「拦截」而不只是记录,在 check 阶段判断权限并调 showmessage() 中断。但这是主动拦截,会在删除流程中间抛提示,逻辑要写得保守。
案例三:改写提示与输出
HookId_message 拦 showmessage(),HookId_output 在模板输出前动手。前者改文案,后者改变量。
<?php
class plugin_hello_forum extends plugin_hello {
// showmessage() 执行时
function forum_message($value) {
$message = $value['param']['message'];
// 把系统提示替换成自己的文案
$message = str_replace('操作成功', '已按站点规则处理完成', $message);
$value['param']['message'] = $message;
// 改完必须返回
return $value;
}
// 模块执行完、模板输出前
function forum_output($value) {
// values 是可用模板变量,往里塞新变量
$value['values']['hello_tip'] = '本页面由 hello 插件增强';
return $value;
}
}
模板侧直接用就行:
{template common/header}
<p>{$hello_tip}</p>
{template common/footer}
forum_message 适合做全站提示文案的品牌化——把「操作成功」换成你自己的措辞。$value['param']['message'] 改完一定要 return,否则提示还是原文。forum_output 更常用:values 里加的变量,模板可以像用系统变量一样用。
怎么查全部预定义钩子
不要背钩子名,也不要靠猜。官方给的路径是在 config/config_global.php 文件末尾加:
$_config['plugindeveloper'] = 2;
改完更新缓存,打开任意页面,查看网页源码搜索 <hook>,所有钩子的实际位置都会标出来。plugindeveloper 为 1 时只显示开发者入口,为 2 时才输出钩子标记。查完记得改回 1 或 0,2 会让页面源码变脏。
钩子命名规律
名字一般拆成四段:类别_脚本名_作用_标识。前两段决定在哪调用,_output 结尾表示输出前,_message 结尾表示 showmessage() 时。规律好记,但具体某个钩子到底存不存在,仍以源码搜索为准。
常用钩子速查
| 方法 | 时机 | 参数 |
|---|---|---|
common() |
所有模块执行前 | 无 |
HookId() |
所有模块执行前(脚本类) | 无 |
HookId_output($value) |
模块执行完、模板输出前 | template、message、values |
global_HookId() |
同上(全局类) | 无 |
HookId_message($value) |
showmessage() 执行时 |
param |
discuzcode($value) |
帖子内容解析时 | param、caller |
deletethread($value) |
主题删除前后,2 次 | param、step |
deletepost($value) |
帖子删除前后,2 次 | param、step |
ad_adId($value) |
对应广告位 | params、content |
avatar($value) |
头像调用时 | — |
ad_adId 里的 adId 是广告位脚本 ID,对应函数名如 ad_headerbanner()。头像钩子 avatar() 是 X2.5 新增的。
小结
三个案例对应三种改法:discuzcode() 在渲染时加工内容(不改数据),deletethread() 在删除前后分段处理(step 判阶段),HookId_message() 改文案、HookId_output() 改变量。
三条通用纪律:改完必须 return $value、分阶段的钩子先判 step、钩子名不确定就开 plugindeveloper = 2 查源码。
第三篇开始讲插件的工程化:XML 清单、安装脚本与数据表。
