轻墨插件开发指南
轻墨插件开发指南:用钩子给博客开“外挂”
插件是轻墨 v2.0 起给第三方留的扩展位:不用改任何核心文件,就能往前台加功能、改文章输出、发通知邮件;v2.1 又补上了插件自己的后台设置页。整件事由一套小小的“钩子”系统支撑。读完本文 + 手写一个示例插件,你就能玩转它。
一个插件 = 一个文件夹(可带设置页)
把插件放进站点根目录的 plugins/ 下,每个插件一个文件夹:
plugins/我的插件/
├── plugin.json # 名片:在后台“插件市场”里显示的信息(必需)
├── plugin.php # 灵魂:被系统加载时注册钩子的代码(必需)
└── admin.php # 可选:后台「设置」页(v2.1 起,启用后会多出「设置」入口)
plugin.json 长这样(纯 JSON):
{
"name": "页脚问候语",
"description": "在每页底部追加一句问候(演示 qm_footer 钩子)。",
"version": "1.0.0",
"author": "你"
}
plugin.php 的最简骨架:
<?php
// 第一行是安全护栏:插件被直接放网址访问时(绕过系统加载),直接拒绝
if (!defined('QM_BOOT')) { exit('Access denied'); }
add_action('qm_footer', function () {
echo '<p class="qm-greet" style="font-size:12px;color:#999;">感谢阅读,欢迎常来。</p>';
});
启用方式:后台 →「插件市场」→ 找到它点「启用」。刷新前台页面底部就多了一行字。
工作机制一句话:系统在运行时把 active_plugins(存在配置里)里每个启用插件的主文件 include_once 进来;插件在“加载那一刻”用 add_action / add_filter 把自己注册到钩子上,之后系统在关键位置触发这些钩子,你的代码就跑起来了。停用 = 不再加载,文件不执行,什么副作用都没有。
让插件有后台设置页(v2.1+)
早期插件没有“填写入口”,文案只能写死在代码里(比如备案插件一度改一行字要动文件)。v2.1 起:只要插件启用且自带了 admin.php,插件市场就会在操作列显示「设置」链接,点开是一个已登录鉴权、已套好后台布局的独立页面——表单、保存逻辑全部由你的插件自己负责。
admin.php 模板:
<?php
if (!defined('QM_BOOT')) { exit('Access denied'); }
// 本文件由 admin/plugin-settings.php 在登录后 include,CSRF 字段自己加
$ok = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!verify_csrf($_POST['csrf_token'] ?? '')) {
echo '<div class="msg error">安全验证失败</div>';
} else {
$file = DATA_DIR . '/plugin_hello.php';
$data = load_data($file, []);
$data['text'] = trim($_POST['text'] ?? '');
save_data($file, $data);
$ok = '已保存。';
}
}
$text = load_data(DATA_DIR . '/plugin_hello.php', [])['text'] ?? '';
if ($ok) echo '<div class="msg success">' . e($ok) . '</div>';
?>
<form method="post" class="admin-form" style="max-width:520px;">
<?php csrf_field(); ?>
<label>问候文字</label>
<textarea name="text" style="height:80px;"><?php echo e($text); ?></textarea>
<p><input type="submit" value="保存"></p>
</form>
配套地,plugin.php 里不再写死文案,而是从同一个数据文件读:
$text = trim((string)load_data(DATA_DIR . '/plugin_hello.php', [])['text'] ?? '');
if ($text !== '') {
add_action('qm_footer', function () use ($text) {
echo '<p class="qm-hello">' . e($text) . '</p>';
});
}
看活例子:plugins/footer-beian/admin.php(页脚备案)、plugins/sidebar-notice/admin.php(侧栏公告)就是这么写的,后台改一行字不用再碰代码。
钩子地图:哪里能“插手”
qm_head(动作):前台<head>内 —— 适合注入 CSS、统计脚本、字体;qm_sidebar(动作):侧边栏各小部件之后 —— 往里塞自定义小部件;qm_sidebar_links(动作):侧边栏“链接”列表内 —— 追加链接;qm_footer(动作):页脚区域 —— 追加版权/备案/问候;qm_post_content(过滤器):单篇文章正文输出前,参数$content, $post—— 改正文;qm_post_footer(过滤器):文章页脚(标题/链接/作者/协议)输出前,参数$html, $post;qm_admin_footer(动作):后台页面底部。
v2.1 起,后台与列表也开放了扩展点(置顶插件 plugins/qingmo-sticky 正是用它们实现的):
qm_posts_list(过滤器):load_posts()排好序后的文章数组 —— 可重新排序(置顶)、增删文章;qm_post_row_actions(过滤器):后台文章列表每行的操作 HTML,参数$actions, $post, $token—— 往里追加按钮(置顶/自定义操作);qm_admin_posts_head(动作):后台「文章管理」页顶部(鉴权后、输出前)—— 处理 GET 类切换请求并跳转。
- 动作(do_action):到点喊一嗓子,你在回调里
echo或做事即可; - 过滤器(apply_filters):把系统的一个值交给你“加工”后再还回去,必须
return加工后的值。
add_action($钩子, $回调, $优先级=10)、add_filter($钩子, $回调, $优先级=10, $接受参数个数)。回调建议用带自己前缀的具名函数或匿名函数,避免和别的插件撞名。
示例一:给文章正文“加戏”(过滤器)
写一个在每篇文章末尾追加小尾巴的插件。因为正文早已渲染成 HTML(Markdown 模式也在保存时转成 HTML),你处理的是 HTML 字符串:
<?php
if (!defined('QM_BOOT')) { exit('Access denied'); }
add_filter('qm_post_content', function ($html, $post) {
$tail = '<p class="qm-post-tail" style="margin-top:18px;padding:10px 14px;'
. 'border:1px dashed #ccc;font-size:13px;color:#666;">'
. '—— 本文来自「' . e($post['title'] ?? '') . '」,欢迎留言讨论。</p>';
return $html . $tail;
}, 10, 2);
要点:
- 签名固定两个参数
$content(正文 HTML)和$post(文章数组,可读title、id等); - 一定要
return,否则前台文章会“消失”; - 输出任何文本前用系统提供的
e()转义,尤其是来自用户的内容。
示例二:往侧栏塞一个小部件(动作)
<?php
if (!defined('QM_BOOT')) { exit('Access denied'); }
add_action('qm_sidebar', function () {
echo '<div class="box qm-sidebar-about">'
. '<h3>关于作者</h3>'
. '<p style="font-size:13px;">你好,我是「'
. e(get_setting('site_title')) . '」的维护者。</p>'
. '</div>';
});
顺带一提 qm_head 的常见用法是注入统计脚本或外部字体:
add_action('qm_head', function () {
echo '<link rel="stylesheet" href="https://example.com/extra.css">';
});
插件能用哪些系统能力
插件加载时,轻墨的核心函数都可用,常用的有:
- 读配置:
get_setting('键', 默认值)—— 读取站点设置项; - 输出转义:
e($str)—— HTML 转义,输出用户内容必用; - 数据读取:
load_data($文件路径, $默认)—— 读取 PHP return 数组文件; - 数据保存:
save_data($文件路径, $数组)—— 自动生成数据文件(带请求内缓存); - 文章/评论/友链(只读):
load_posts()、load_post($id)、load_categories()、load_comments($post_id)、load_links(); - 站点 URL:
site_base_url()—— 得到站点根地址; - Markdown:
md_to_html($text)—— 把 md 渲染成 HTML; - 发邮件:
qm_send_mail($收件人, $标题, $纯文本, $html=null)—— 走后台 SMTP / mail 配置。
插件想存自己的数据怎么办? 数据目录受 .htaccess / Nginx 规则保护,外部访问不到,是理想的插件存储位置:
// 示例:记录一个简单的“公告文案”
$file = DATA_DIR . '/plugin_announce.php';
$data = load_data($file, []);
$data['text'] = '欢迎来到本站';
save_data($file, $data);
DATA_DIR 常量指向 data/ 目录。注意所有数据文件都以 <?php return ...; 格式存储,由 save_data 生成,别手写奇怪格式。
发提醒邮件同样简单(前提:后台已配置 SMTP 或服务器支持 PHP mail):
qm_send_mail('someone@example.com', '你好', "这是一封来自轻墨插件的邮件。\n第二行。");
三个会踩的坑
- 忘记 return(过滤器专用)。动作钩子你随便 echo;过滤器你
add_filter后若回调没return,文章正文就没了。写完先开一篇草稿文试。 - 函数/全局撞名。插件可能和别的插件或未来核心函数重名。具名函数加自己前缀(
myplugin_xxx),变量尽量写在函数/闭包内。 - 把用户数据直接 echo。昵称、评论内容都来自访客,输出前一律过
e(),不然等于给站点开 XSS 后门。
调试与分发
- 改完
plugin.php/admin.php直接刷新对应页面即可(每次请求都重新加载),觉得没生效先Ctrl+F5清浏览器缓存; - 出错了不知道哪来的,先在
plugin.php顶部临时error_log('here');,再打开后台看一眼php_errors.log之类日志; - 分发 = 把
plugin.json + plugin.php(如有admin.php一并)打包成 zip,别人解压进plugins/后到「插件市场」启用,需要填写的就能点「设置」; - 卸载 = 后台停用,然后删掉整个文件夹(设置存在
data/下的独立数据文件里,会一并删)。
内置插件是最好的活教材:footer-beian(页脚备案,动作钩子 + 设置页示例)、post-copyright(文章版权尾巴,过滤器)、sidebar-notice(侧栏公告,动作钩子 + 设置页示例)、qingmo-sticky(文章置顶,列表排序/行操作钩子示例)。直接对着读一遍胜过十篇教程。
一句话版本:plugin.json 声明自己,plugin.php 用 add_action / add_filter 挂到钩子上;想给站长可填的配置,再加一个带表单的 admin.php 设置页。动作钩子做事、过滤器钩子改值;输出必转义、过滤器必 return。
评论 (0)
还没有评论,来抢沙发吧~
发表评论