EnovelCms 插件开发文档
EnovelCms 插件开发文档
目录
概述
插件基本结构
插件描述文件 plugin.json
插件主入口 main.php
钩子系统 (Hooks)
Action 钩子 (动作)
Filter 钩子 (过滤器)
钩子优先级
完整钩子列表
系统钩子
前端页面钩子
后端管理钩子
用户中心钩子
数据过滤器
插件安装/卸载脚本
install.php
uninstall.php
插件管理页面
数据库操作建议
开发示例
示例一:自定义用户等级称号
示例二:小说增加签约状态管理
示例三:前端底部添加统计代码
示例四:修改首页返回数据
常见问题
附录:插件打包与分发
概述
EnovelCms 采用钩子(Action/Filter)机制实现插件系统,允许开发者在不修改核心代码的前提下扩展系统功能。插件只需放置在 plugins/ 目录下,即可通过后台安装、启用、停用、卸载。
插件可以:
在前端页面任意位置插入 HTML 内容
在后台管理界面增加菜单、表单字段、列表项
修改前端数据查询结果(Filter)
扩展用户中心的功能(等级、积分、额外资料等)
添加自定义数据库表,实现复杂业务逻辑
注意:核心代码不会再修改,所有新增功能均应通过插件实现。
插件基本结构
一个标准的插件目录应包含:
plugins/ └── my_plugin/ # 插件目录名(唯一标识) ├── plugin.json # 插件描述文件(必需) ├── main.php # 插件主入口(必需,启用时加载) ├── install.php # 安装脚本(可选) ├── uninstall.php # 卸载脚本(可选) ├── manage.php # 后台管理页面(可选) └── assets/ # 插件静态资源(可选)
插件描述文件 plugin.json
{
"name": "插件显示名称",
"version": "1.0.0",
"description": "插件功能简介",
"author": "作者名",
"url": "https://example.com/plugin"}name: 插件在后台显示的名称version: 版本号description: 简要描述(可选)author: 作者(可选)url: 插件主页(可选)
系统读取 plugin.json 来注册插件信息,必须包含 name 和 version。
插件主入口 main.php
main.php 在插件被启用后每次页面请求都会自动加载。在此文件中注册所有钩子。
<?php/**
* 插件示例:我的插件
*/// 注册 Action 钩子PluginManager::register('frontend_footer', 'my_plugin_footer_output', 10, 'action');function my_plugin_footer_output() {
echo '<div style="text-align:center; padding:10px;">Powered by My Plugin</div>';}// 注册 Filter 钩子PluginManager::register('home_data', 'my_plugin_modify_home_data', 10, 'filter');function my_plugin_modify_home_data($data) {
// 修改首页数据
$data['extra_message'] = 'Hello from plugin!';
return $data;}PluginManager::register() 的四个参数:
钩子名称
回调函数(或类方法数组)
优先级(默认 10,数字越小执行越早)
类型:
'action'或'filter'
你也可以直接使用对象方法:
$pm = PluginManager::getInstance();$pm->addAction('hook_name', [$this, 'method']);$pm->addFilter('hook_name', [$this, 'method']);钩子系统 (Hooks)
Action 钩子 (动作)
Action 钩子用于在特定位置输出内容或执行操作。不返回值。
注册:
PluginManager::register('钩子名', '函数名', 优先级, 'action');调用(系统内部):
PluginManager::getInstance()->doAction('钩子名', $参数1, $参数2, ...);插件接收参数示例:
function my_func($arg1, $arg2) {
echo $arg1 . $arg2;}Filter 钩子 (过滤器)
Filter 钩子用于修改数据。回调函数必须接收并返回数据。
注册:
PluginManager::register('钩子名', '函数名', 优先级, 'filter');调用:
$data = PluginManager::getInstance()->applyFilters('钩子名', $原始数据, $额外参数...);插件示例:
function modify_data($data, $extra_arg) {
$data['new_field'] = $extra_arg;
return $data;}钩子优先级
优先级范围为 1~∞,数字越小执行越早。如果多个插件注册了同一钩子,按优先级排序,同优先级按注册顺序执行。
完整钩子列表
以下列出 EnovelCms 中所有可用的钩子。带 $参数 表示传递的参数。
系统钩子
| 钩子名称 | 类型 | 说明 | 参数 |
|---|---|---|---|
plugin_init | Action | 所有插件加载完毕后触发 | 无 |
前端页面钩子
| 钩子名称 | 类型 | 说明 | 参数 |
|---|---|---|---|
frontend_head | Action | 出现在 </head> 之前,适合添加 meta/link/script | 无 |
frontend_nav_after | Action | 主导航栏菜单之后,可追加导航项 | 无 |
frontend_home_before | Action | 首页内容区域顶部 | 无 |
frontend_home_after | Action | 首页内容区域底部 | 无 |
frontend_novel_detail_before | Action | 小说详情页顶部 | $novel 数组 |
frontend_novel_detail_info_middle | Action | 小说详情页中部(统计与简介之间) | $novel 数组 |
frontend_novel_detail_after | Action | 小说详情页底部 | $novel 数组 |
frontend_read_before | Action | 章节阅读页顶部 | $chapter 数组 |
frontend_read_after | Action | 章节阅读页底部 | $chapter 数组 |
frontend_footer | Action | 全站底部,位于 </script> 之前 | 无 |
后端管理钩子
左侧菜单栏
| 钩子名称 | 类型 | 说明 | 参数 |
|---|---|---|---|
admin_sidebar | Action | 侧边栏菜单列表末尾,可插入新菜单项 | 无 |
小说管理 (admin/novels.php)
| 钩子名称 | 类型 | 说明 | 参数 |
|---|---|---|---|
admin_novels_before_form | Action | 小说管理页面整体内容之前 | 无 |
admin_novels_after_form | Action | 小说管理页面整体内容之后 | 无 |
admin_novels_form_before | Action | 添加/编辑小说表单之前 | 无 |
admin_novels_form_after | Action | 添加/编辑小说表单之后 | 无 |
admin_novels_form_fields | Action | 表单中额外字段插入位置(在简介之后) | 无 |
admin_novels_list_th | Action | 小说表格表头末尾,可增加 <th> | 无 |
admin_novels_list_td | Action | 小说表格行末尾,可增加 <td> | $novel 数组(当前行数据) |
admin_novels_list_actions_before | Action | 操作按钮之前 | $novel 数组 |
admin_novels_list_actions_after | Action | 操作按钮之后 | $novel 数组 |
admin_novels_list_footer | Action | 小说列表下方(分页之前) | 无 |
admin_novels_save | Action | 小说保存成功后触发 | $id, $isNew, $postData |
admin_novels_delete | Action | 小说删除后触发 | $id |
用户管理 (admin/users.php)
| 钩子名称 | 类型 | 说明 | 参数 |
|---|---|---|---|
admin_users_before_table | Action | 用户表格之前 | 无 |
admin_users_after_table | Action | 用户表格之后 | 无 |
admin_users_list_th | Action | 用户表头末尾 | 无 |
admin_users_list_td | Action | 用户行末尾 | $user 数组 |
admin_users_list_actions_before | Action | 操作按钮前 | $user 数组 |
admin_users_list_actions_after | Action | 操作按钮后 | $user 数组 |
用户编辑 (admin/edit_user.php)
| 钩子名称 | 类型 | 说明 | 参数 |
|---|---|---|---|
admin_edit_user_form_before | Action | 编辑用户表单前 | $user 数组 |
admin_edit_user_form_after | Action | 编辑用户表单后 | $user 数组 |
admin_edit_user_form_fields | Action | 表单中额外字段位置 | $user 数组 |
admin_edit_user_save | Action | 保存用户数据后 | $userId, $data (提交的数据) |
用户中心钩子
通用侧边栏
| 钩子名称 | 类型 | 说明 | 参数 |
|---|---|---|---|
user_center_sidebar_after | Action | 所有用户中心页面侧边栏菜单之后 | $user 数组 |
用户首页 (user/index.php)
| 钩子名称 | 类型 | 说明 | 参数 |
|---|---|---|---|
user_center_content_before | Action | 内容区域顶部 | $user 数组 |
user_center_content_after | Action | 内容区域底部 | $user 数组 |
user_index_info_card_after | Action | 基本信息卡片之后 | $user 数组 |
user_index_extra | Action | 统计区块之后,签到提示之前 | $user 数组 |
个人资料 (user/profile.php)
| 钩子名称 | 类型 | 说明 | 参数 |
|---|---|---|---|
user_profile_form_before | Action | 表单之前 | $user 数组 |
user_profile_form_after | Action | 表单之后 | $user 数组 |
user_profile_extra_fields | Action | 密码字段后、提交按钮前 | $user 数组 |
书架 (user/bookshelf.php)
| 钩子名称 | 类型 | 说明 | 参数 |
|---|---|---|---|
user_bookshelf_before_list | Action | 书架列表前 | $user 数组 |
user_bookshelf_after_list | Action | 书架列表后 | $user 数组 |
user_bookshelf_item_extra | Action | 每条书架项目信息区 | $book 数组 |
阅读历史 (user/history.php)
| 钩子名称 | 类型 | 说明 | 参数 |
|---|---|---|---|
user_history_before_list | Action | 历史列表前 | $user 数组 |
user_history_after_list | Action | 历史列表后 | $user 数组 |
user_history_th | Action | 表头末尾 | 无 |
user_history_td | Action | 每行末尾 | $historyItem 数组 |
金币日志 (user/gold.php)
| 钩子名称 | 类型 | 说明 | 参数 |
|---|---|---|---|
user_gold_before_list | Action | 日志列表前 | $user 数组 |
user_gold_after_list | Action | 日志列表后 | $user 数组 |
user_gold_th | Action | 表头末尾 | 无 |
user_gold_td | Action | 每行末尾 | $logEntry 数组 |
签到 (user/sign.php)
| 钩子名称 | 类型 | 说明 | 参数 |
|---|---|---|---|
user_sign_extra | Action | 签到卡片下方 | $rewardPreview, $todaySigned |
VIP 中心 (user/vip.php)
| 钩子名称 | 类型 | 说明 | 参数 |
|---|---|---|---|
user_vip_before_forms | Action | 兑换/充值表单之前 | $user 数组 |
user_vip_between_forms | Action | 两种充值方式之间 | $user 数组 |
user_vip_after_forms | Action | 所有表单之后 | $user 数组 |
数据过滤器 (Filter)
这些过滤器可以让你修改最终传递给模板的数据。
| 过滤器名称 | 说明 | 传递额外参数 |
|---|---|---|
home_data | 首页数据 | 无 |
library_data | 书库数据 | 无 |
rank_data | 排行榜数据 | 无 |
author_data | 作者列表数据 | 无 |
novel_detail_data | 小说详情数据 | $id (小说ID) |
read_data | 阅读页数据 | $novelId, $chapterId |
search_data | 搜索结果数据 | 无 |
user_index_data | 用户首页数据 | $userId |
user_profile_data | 个人资料数据 | $userId |
user_bookshelf_data | 书架数据 | $userId |
user_history_data | 阅读历史数据 | $userId |
user_gold_data | 金币日志数据 | $userId |
user_sign_data | 签到数据 | $userId |
user_vip_data | VIP 中心数据 | $userId |
所有 Filter 钩子接收第一个参数为 $data 数组,必须返回修改后的数组。
示例:
function my_novel_detail_filter($data, $novelId) {
// 给小说详情增加一个自定义字段
$data['custom_field'] = 'some value';
return $data;}PluginManager::register('novel_detail_data', 'my_novel_detail_filter', 10, 'filter');插件安装/卸载脚本
install.php
安装插件时执行(通过后台点击“安装”触发)。通常用于创建数据库表。
<?phpglobal $db;$db->query("CREATE TABLE IF NOT EXISTS `my_plugin_data` (
`id` int NOT NULL AUTO_INCREMENT,
`novel_id` int NOT NULL,
`signed_status` tinyint NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;");uninstall.php
卸载插件时执行。通常用于清理数据库。
<?phpglobal $db;$db->query("DROP TABLE IF EXISTS `my_plugin_data`;");注意:卸载时程序会尝试删除插件整个目录,但若文件权限不足可能失败,此时仅删除数据库记录。
插件管理页面
如果插件需要后台管理界面,可在插件目录下创建 manage.php。后台访问 admin/plugins.php?action=manage&plugin=my_plugin 时将会包含该文件。
manage.php 中可直接使用全局变量 $db, $pm 等,并输出 HTML。
简单示例:
<?php if (!defined('ROOT_PATH')) exit; // 防止直接访问 ?><h3>我的插件设置</h3><form method="post">
<label>设置项:<input type="text" name="setting_value"></label>
<button type="submit">保存</button></form>你可以在 manage.php 中处理 POST 数据,保存到 settings 表或自己的表中。
数据库操作建议
使用全局
$db对象执行 SQL(基于 PDO/MySQLi 封装)。插件自定义表建议以插件目录名作为表前缀,例如
my_plugin_data。在
install.php中建表,uninstall.php中清理。使用
getSetting()/setSetting()函数可方便存取系统的settings表(键值对),适合简单配置。
示例:
// 读取$value = getSetting('my_plugin_option', $db);// 写入$db->query("REPLACE INTO settings (`key`, `value`) VALUES ('my_plugin_option', ?)", [$newValue]);开发示例
示例一:自定义用户等级称号
目标:在用户中心展示头衔“青铜作者”、“白银作者”等,并在用户编辑页面可设置。
目录
plugins/user_titleplugin.json:
{
"name": "用户头衔",
"version": "1.0.0",
"description": "为用户增加自定义头衔",
"author": "Your Name"}install.php:为users表增加字段title。
<?phpglobal $db;$db->query("ALTER TABLE users ADD COLUMN title VARCHAR(50) DEFAULT '' AFTER username");uninstall.php:
<?phpglobal $db;$db->query("ALTER TABLE users DROP COLUMN title");main.php:
<?php// 注册钩子PluginManager::register('user_index_info_card_after', 'show_user_title', 10, 'action');PluginManager::register('admin_edit_user_form_fields', 'add_title_field', 10, 'action');PluginManager::register('admin_edit_user_save', 'save_title_field', 10, 'action');function show_user_title($user) {
if (!empty($user['title'])) {
echo '<div class="user-title-badge">头衔:' . h($user['title']) . '</div>';
}}function add_title_field($user) {
echo '<div><label>头衔</label><input type="text" name="title" value="' . h($user['title']) . '"></div>';}function save_title_field($userId, $data) {
global $db;
$title = isset($_POST['title']) ? trim($_POST['title']) : '';
$db->query("UPDATE users SET title = ? WHERE id = ?", [$title, $userId]);}示例二:小说增加签约状态管理
目标:在小说管理后台增加“签约状态”字段(已签约/未签约),并在前端详情页展示。
目录
plugins/novel_signedinstall.php:为novels表增加is_signed字段。
<?phpglobal $db;$db->query("ALTER TABLE novels ADD COLUMN is_signed TINYINT NOT NULL DEFAULT 0 AFTER status");uninstall.php:
<?phpglobal $db;$db->query("ALTER TABLE novels DROP COLUMN is_signed");main.php:
<?php// 后台:表单增加签约选项PluginManager::register('admin_novels_form_fields', 'signed_form_field', 10, 'action');// 后台:保存时处理PluginManager::register('admin_novels_save', 'save_signed_status', 10, 'action');// 后台:列表显示状态PluginManager::register('admin_novels_list_td', 'list_signed_status', 10, 'action');// 前端详情:显示签约标识PluginManager::register('frontend_novel_detail_info_middle', 'show_signed_badge', 10, 'action');// Filter:确保数据中包含 is_signedPluginManager::register('novel_detail_data', 'add_signed_to_data', 10, 'filter');function signed_form_field() {
// 从 GET 或已存数据读取当前值
echo '<div class="form-field"><label>签约状态</label>
<select name="is_signed">
<option value="0">未签约</option>
<option value="1">已签约</option>
</select></div>';}function save_signed_status($id, $isNew, $postData) {
global $db;
$is_signed = isset($_POST['is_signed']) ? (int)$_POST['is_signed'] : 0;
$db->query("UPDATE novels SET is_signed = ? WHERE id = ?", [$is_signed, $id]);}function list_signed_status($novel) {
echo '<td>' . ($novel['is_signed'] ? '已签约' : '未签约') . '</td>';}function show_signed_badge($novel) {
if (!empty($novel['is_signed'])) {
echo '<span class="badge badge-signed">已签约</span>';
}}function add_signed_to_data($data, $id) {
global $db;
$novel = $db->fetch($db->query("SELECT is_signed FROM novels WHERE id = ?", [$id]));
if ($novel) {
$data['novel']['is_signed'] = $novel['is_signed'];
}
return $data;}示例三:前端底部添加统计代码
<?phpPluginManager::register('frontend_footer', function() {
echo '<script>
console.log("Plugin loaded");
</script>';});示例四:修改首页数据
<?phpfunction add_home_message($data) {
$data['welcome_message'] = 'Welcome to our novel site!';
return $data;}PluginManager::register('home_data', 'add_home_message', 10, 'filter');然后在首页模板中,你可以判断 $welcome_message 并输出。
常见问题
Q: 多个插件注册了同一个钩子,执行顺序如何?
A: 按优先级从小到大执行。同优先级则按注册顺序(先注册先执行)。可通过第三个参数 priority 控制。
Q: 插件可以在 main.php 之外加载文件吗?
A: 可以。main.php 只是入口,你可以 require_once 其他文件。
Q: 如何获取当前用户信息?
A: 使用全局 $_SESSION['user_id'] 和 $_SESSION['admin'] 判断登录状态。前端页面中有 $user 变量。
Q: 插件可以修改核心模板吗?
A: 不能直接修改模板文件,但可通过 Action 钩子插入 HTML,或通过 Filter 修改传给模板的数据,从而实现模板定制。
Q: 插件卸载后数据库字段还在怎么办?
A: 在 uninstall.php 中写清理逻辑。如果忘了写,用户可手动清理。
Q: 如何让插件支持多语言?
A: 系统提供了 $lang 对象。在你的插件中可访问 $GLOBALS['lang']->get('your_key'),但需要事先在语言包中增加翻译。你也可以插件自带语言文件,自行实现。
附录:插件打包与分发
将插件目录打成 .zip 包,用户可以通过后台“插件管理”手动上传解压到 plugins/ 目录,然后安装。目前版本尚未提供直接上传安装界面,用户需手动复制目录。
建议目录结构:
my_plugin.zip └── my_plugin/ ├── plugin.json ├── main.php ├── install.php (可选) ├── uninstall.php (可选) ├── manage.php (可选) └── assets/ (可选)
让用户将解压后的 my_plugin 文件夹放入 plugins/ 目录,再到后台插件管理里点击“安装”即可。
通过以上文档,你可以自由开发任意功能的插件,无需修改 EnovelCms 核心代码。如有更多钩子需求,欢迎向系统开发者反馈。