纸鱼博客插件用于增加站点功能、监听系统事件、修改系统数据或输出前台内容。插件放在站点根目录的 plugins/ 下,每个插件使用一个独立目录。
插件目录
插件目录名只能使用小写字母、数字、下划线和连字符,长度为 1 到 50 个字符。
最小插件结构:
plugins/example/
├── plugin.json
└── index.phpplugin.json 描述插件,index.php 返回插件实现函数数组。
plugin.json
name 必须与目录名一致,title 和 version 为必填项:
{
"name": "example",
"title": "示例插件",
"version": "1.0.0",
"apiVersion": 2,
"description": "一个 Pafish 插件",
"author": "你的名字",
"authorUrl": "https://example.com",
"homepage": "https://example.com/example-plugin",
"settings": [],
"injects": []
}apiVersion 当前支持 1 和 2,建议使用 2。v2 插件只有在 injects 中声明过的前台注入点才会输出内容。
插件入口
index.php 必须返回数组。最常用的入口是 registerHooks:
<?php
return [
'registerHooks' => function (object $ctx): void {
$ctx->on('after_login', function (array $user) use ($ctx): void {
$ctx->log('用户登录:' . ($user['username'] ?? ''));
});
},
];
插件每次请求启动时都会注册钩子。停用插件后,系统会自动移除该插件注册的钩子。
插件上下文 ctx
registerHooks、生命周期和渲染函数都会收到插件上下文对象:
方法 | 用途 |
|---|---|
| 注册 action 事件 |
| 注册 filter 过滤器 |
| 读写插件自有数据 |
| 读写插件设置 |
| 写入插件日志,最多保留 50 条 |
| 刷新注入内容;PHP 版即时渲染,无需额外操作 |
插件数据和插件设置分别保存在系统设置表的 plugin_data:{name} 与 plugin_settings:{name} 中,使用 JSON 对象存储。
系统钩子
Action 事件
通过 $ctx->on() 注册:
钩子 | 参数 |
|---|---|
| 用户数组: |
| 用户 ID 数组 |
| 用户数组: |
| 保存事件数组: |
| 新建文章数据 |
| 更新后的文章数据 |
| 已发布文章数据 |
| 移入回收站的文章数据 |
| 永久删除的文章数据 |
| 评论数据 |
| 评论状态变更数据 |
| 回复评论数据 |
| 被删除的评论数据 |
示例:
'registerHooks' => function (object $ctx): void {
$ctx->on('after_post_published', function (array $post) use ($ctx): void {
$ctx->log('文章已发布:' . ($post['title'] ?? ''));
});
},Filter 过滤器
通过 $ctx->filter() 注册。过滤器的第一个参数是当前值,回调必须返回修改后的值:
'registerHooks' => function (object $ctx): void {
$ctx->filter('frontend_meta', function (array $meta, array $context): array {
$meta['robots'] = 'noindex';
return $meta;
});
},当前常用过滤器包括:
before_login:登录前检查或修改登录决定before_register:注册前检查或修改注册决定before_comment_submit:评论写入前审核或拦截frontend_meta:修改页面标题、描述、canonical、Open Graph 和 JSON-LDupload_store_to_cloud:接管文件上传并返回云端 URLupload_delete_from_cloud:删除云端文件时收到通知
过滤器应始终返回符合原接口约定的数据。需要阻止操作时,请返回带有 allowed: false 的决定数组,并提供清晰的 error 信息。
前台注入
在 plugin.json 中声明注入位置:
{
"apiVersion": 2,
"injects": ["head", "footer", "sidebar", "comment_form"]
}然后在 index.php 中提供 renderInjection:
'renderInjection' => function (string $target, object $ctx, array $context): string {
if ($target !== 'footer') {
return '';
}
return '<p class="plugin-note">由示例插件提供</p>';
},支持的注入点:head、footer、sidebar、comment_form、login_form、register_form、post_editor。
head 注入只允许 script、meta、link 和 style 标签;其他位置请自行保证输出安全。
插件设置
在 plugin.json 的 settings 中声明后台设置项:
{
"settings": [
{
"key": "enabled_message",
"label": "提示内容",
"type": "text",
"group": "基本设置",
"default": "欢迎使用 Pafish"
},
{
"key": "show_on_footer",
"label": "显示在页脚",
"type": "switcher",
"default": "1"
}
]
}支持的类型:text、textarea、password、checkbox、switcher、select、radio、color 和 image。选项型字段使用 options 对象。
插件中读取设置:
$settings = $ctx->getSettings();
$message = (string) ($settings['enabled_message'] ?? '');生命周期
可以在 index.php 中定义以下回调:
return [
'onActivate' => function (object $ctx): void {
$ctx->log('插件已启用');
},
'onDeactivate' => function (object $ctx): void {
// 停用时释放外部资源或清理运行状态
},
'onUninstall' => function (object $ctx): void {
// 卸载前清理自有数据
},
];卸载时系统会在删除插件目录前调用 onUninstall,随后删除插件设置和自有数据。重要数据请在回调中完成清理或导出。
插件页面和页面模板
页面模板
在清单中声明页面模板:
{
"pageTemplates": [
{"name": "card", "title": "卡片页"}
]
}再实现 renderPageTemplate($template, $page, $ctx)。返回空字符串或 null 时,系统会继续使用默认页面渲染。
插件前台页面
在清单中声明页面路径:
{
"pages": [
{"path": "dashboard", "title": "插件面板"}
]
}实现 renderPluginPage($path, $ctx) 后,页面地址为:
/plugin/example/dashboard页面只有在插件已激活、路径已声明且渲染函数返回非空 HTML 时才会显示。
安装和发布
将插件目录放入
plugins/,或在后台“应用商店”中安装。检查插件清单是否通过校验。
在后台“插件”页面启用插件并配置设置。
测试前台页面、后台功能和停用后的回退行为。
发布时将插件目录压缩为 zip;压缩包只能包含一个顶层目录,大小不能超过 10MB。
开发建议
使用 API v2,并在
plugin.json中明确声明injects。插件名称、设置键和页面路径一旦发布,尽量保持不变。
所有 HTML 输出和用户输入都要进行转义或严格校验。
不要直接修改纸鱼博客核心文件;优先使用钩子、主题和插件接口。
外部请求应设置超时,并处理网络失败,不要阻塞页面请求。
不要把密钥写入代码或提交到公开仓库。