EnovelCms 插件开发文档

教程文章 2026年05月10日 EnovelCms官网

EnovelCms 插件开发文档

目录

  1. 概述

  2. 插件基本结构

  3. 插件描述文件 plugin.json

  4. 插件主入口 main.php

  5. 钩子系统 (Hooks)

    • Action 钩子 (动作)

    • Filter 钩子 (过滤器)

    • 钩子优先级

  6. 完整钩子列表

    • 系统钩子

    • 前端页面钩子

    • 后端管理钩子

    • 用户中心钩子

    • 数据过滤器

  7. 插件安装/卸载脚本

    • install.php

    • uninstall.php

  8. 插件管理页面

  9. 数据库操作建议

  10. 开发示例

    • 示例一:自定义用户等级称号

    • 示例二:小说增加签约状态管理

    • 示例三:前端底部添加统计代码

    • 示例四:修改首页返回数据

  11. 常见问题

  12. 附录:插件打包与分发


概述

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() 的四个参数:

  1. 钩子名称

  2. 回调函数(或类方法数组)

  3. 优先级(默认 10,数字越小执行越早)

  4. 类型:'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_initAction所有插件加载完毕后触发

前端页面钩子

钩子名称类型说明参数
frontend_headAction出现在 </head> 之前,适合添加 meta/link/script
frontend_nav_afterAction主导航栏菜单之后,可追加导航项
frontend_home_beforeAction首页内容区域顶部
frontend_home_afterAction首页内容区域底部
frontend_novel_detail_beforeAction小说详情页顶部$novel 数组
frontend_novel_detail_info_middleAction小说详情页中部(统计与简介之间)$novel 数组
frontend_novel_detail_afterAction小说详情页底部$novel 数组
frontend_read_beforeAction章节阅读页顶部$chapter 数组
frontend_read_afterAction章节阅读页底部$chapter 数组
frontend_footerAction全站底部,位于 </script> 之前

后端管理钩子

左侧菜单栏

钩子名称类型说明参数
admin_sidebarAction侧边栏菜单列表末尾,可插入新菜单项

小说管理 (admin/novels.php)

钩子名称类型说明参数
admin_novels_before_formAction小说管理页面整体内容之前
admin_novels_after_formAction小说管理页面整体内容之后
admin_novels_form_beforeAction添加/编辑小说表单之前
admin_novels_form_afterAction添加/编辑小说表单之后
admin_novels_form_fieldsAction表单中额外字段插入位置(在简介之后)
admin_novels_list_thAction小说表格表头末尾,可增加 <th>
admin_novels_list_tdAction小说表格行末尾,可增加 <td>$novel 数组(当前行数据)
admin_novels_list_actions_beforeAction操作按钮之前$novel 数组
admin_novels_list_actions_afterAction操作按钮之后$novel 数组
admin_novels_list_footerAction小说列表下方(分页之前)
admin_novels_saveAction小说保存成功后触发$id, $isNew, $postData
admin_novels_deleteAction小说删除后触发$id

用户管理 (admin/users.php)

钩子名称类型说明参数
admin_users_before_tableAction用户表格之前
admin_users_after_tableAction用户表格之后
admin_users_list_thAction用户表头末尾
admin_users_list_tdAction用户行末尾$user 数组
admin_users_list_actions_beforeAction操作按钮前$user 数组
admin_users_list_actions_afterAction操作按钮后$user 数组

用户编辑 (admin/edit_user.php)

钩子名称类型说明参数
admin_edit_user_form_beforeAction编辑用户表单前$user 数组
admin_edit_user_form_afterAction编辑用户表单后$user 数组
admin_edit_user_form_fieldsAction表单中额外字段位置$user 数组
admin_edit_user_saveAction保存用户数据后$userId, $data (提交的数据)

用户中心钩子

通用侧边栏

钩子名称类型说明参数
user_center_sidebar_afterAction所有用户中心页面侧边栏菜单之后$user 数组

用户首页 (user/index.php)

钩子名称类型说明参数
user_center_content_beforeAction内容区域顶部$user 数组
user_center_content_afterAction内容区域底部$user 数组
user_index_info_card_afterAction基本信息卡片之后$user 数组
user_index_extraAction统计区块之后,签到提示之前$user 数组

个人资料 (user/profile.php)

钩子名称类型说明参数
user_profile_form_beforeAction表单之前$user 数组
user_profile_form_afterAction表单之后$user 数组
user_profile_extra_fieldsAction密码字段后、提交按钮前$user 数组

书架 (user/bookshelf.php)

钩子名称类型说明参数
user_bookshelf_before_listAction书架列表前$user 数组
user_bookshelf_after_listAction书架列表后$user 数组
user_bookshelf_item_extraAction每条书架项目信息区$book 数组

阅读历史 (user/history.php)

钩子名称类型说明参数
user_history_before_listAction历史列表前$user 数组
user_history_after_listAction历史列表后$user 数组
user_history_thAction表头末尾
user_history_tdAction每行末尾$historyItem 数组

金币日志 (user/gold.php)

钩子名称类型说明参数
user_gold_before_listAction日志列表前$user 数组
user_gold_after_listAction日志列表后$user 数组
user_gold_thAction表头末尾
user_gold_tdAction每行末尾$logEntry 数组

签到 (user/sign.php)

钩子名称类型说明参数
user_sign_extraAction签到卡片下方$rewardPreview$todaySigned

VIP 中心 (user/vip.php)

钩子名称类型说明参数
user_vip_before_formsAction兑换/充值表单之前$user 数组
user_vip_between_formsAction两种充值方式之间$user 数组
user_vip_after_formsAction所有表单之后$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_dataVIP 中心数据$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]);

开发示例

示例一:自定义用户等级称号

目标:在用户中心展示头衔“青铜作者”、“白银作者”等,并在用户编辑页面可设置。

  1. 目录 plugins/user_title

  2. plugin.json

{
  "name": "用户头衔",
  "version": "1.0.0",
  "description": "为用户增加自定义头衔",
  "author": "Your Name"}
  1. install.php:为 users 表增加字段 title

<?phpglobal $db;$db->query("ALTER TABLE users ADD COLUMN title VARCHAR(50) DEFAULT '' AFTER username");
  1. uninstall.php

<?phpglobal $db;$db->query("ALTER TABLE users DROP COLUMN title");
  1. 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]);}

示例二:小说增加签约状态管理

目标:在小说管理后台增加“签约状态”字段(已签约/未签约),并在前端详情页展示。

  1. 目录 plugins/novel_signed

  2. install.php:为 novels 表增加 is_signed 字段。

<?phpglobal $db;$db->query("ALTER TABLE novels ADD COLUMN is_signed TINYINT NOT NULL DEFAULT 0 AFTER status");
  1. uninstall.php

<?phpglobal $db;$db->query("ALTER TABLE novels DROP COLUMN is_signed");
  1. 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 核心代码。如有更多钩子需求,欢迎向系统开发者反馈。