RyeBlog 文档

RyeBlog 插件开发规范

本文档面向第三方开发者,详细介绍如何为 RyeBlog 开发插件。

插件架构

插件架构

插件是一个 Plugin_{目录名} 类,通过约定方法(如 config()saveConfig()contentMenu())与后台对接,并可在钩子点挂载行为。后台「插件管理」负责启用 / 停用,「插件内容」负责聚合各插件的内容入口。

目录结构

usr/plugins/
└── your-plugin/           ← 插件目录名(仅用字母数字下划线)
    ├── Plugin.php         ← 主文件(必需)
    ├── config.php         ← 配置页面逻辑(可选,由 config() 方法实现)
    ├── assets/            ← 静态资源(可选)
    │   ├── css/
    │   └── js/
    └── lib/               ← 第三方库(可选)

Plugin.php 规范

插件主文件必须定义一个类,类名格式为 Plugin_{目录名}(目录名中的特殊字符替换为下划线)。

元数据注释

在文件头部使用 PHP 注释声明插件元信息,后台通过正则解析:

<?php
/**
 * @Title    插件名称
 * @Desc     插件描述
 * @Version  1.0.0
 * @Author   作者名
 */

最小示例

<?php
/**
 * @Title    我的插件
 * @Desc     一个简单的示例插件
 * @Version  1.0.0
 * @Author   Your Name
 */

class Plugin_my_plugin
{
    // 钩子方法...
}

钩子(Hooks)系统

RyeBlog 在前台和后台的关键位置预留了钩子。插件通过定义与钩子同名的方法来注入内容或修改数据。

Action 钩子(输出 HTML)

doHook($name, $arg) / doAction($name, $arg) 会遍历所有已启用的插件,调用其同名方法,收集返回的 HTML 字符串并拼接输出。

| 钩子名 | 触发位置 | 参数 | 返回值 | |--------|---------|------|--------| | header | <head> 标签内 | 无 | HTML 字符串 | | footer | </body> 标签前 | 无 | HTML 字符串 | | sidebarTop | 侧边栏顶部 | 无 | HTML 字符串 | | sidebarBottom | 侧边栏底部 | 无 | HTML 字符串 | | articleFooter | 文章内容后(评论前) | $post 数组 | HTML 字符串 | | afterArticleContent | 正文 HTML 输出后 | $post 数组 | HTML 字符串 |

注意:钩子方法名使用 驼峰命名(如 sidebarTop),与 doHook('sidebar_top') 的下划线参数对应。RyeBlog 内部会自动匹配驼峰方法名。

Filter 钩子(修改数据)

applyFilter($name, $value, $extra) 遍历所有已启用的插件,调用其同名方法,方法接收一个值并可修改后返回。

| 过滤器名 | 触发位置 | 参数 | 返回值 | |---------|---------|------|--------| | filterContent | 正文渲染后 | $html | 修改后的 HTML |

钩子方法示例

class Plugin_my_plugin
{
    /**
     * 在 <head> 中添加内容
     */
    public static function header()
    {
        return '<meta name="my-plugin" content="active">' . "\n";
    }

    /**
     * 在页脚添加 JavaScript
     */
    public static function footer()
    {
        return '<script>console.log("My Plugin is active!");</script>' . "\n";
    }

    /**
     * 在文章末尾追加内容
     * @param array $post 文章数据,包含 id, title, slug, content 等
     */
    public static function articleFooter($post)
    {
        if (!$post) return '';
        return '<p style="margin-top:20px;color:#999">—— 由我的插件提供</p>';
    }

    /**
     * 在侧边栏顶部添加自定义小工具
     */
    public static function sidebarTop()
    {
        return '<div class="widget"><h3>自定义工具</h3><p>这是我的插件添加的内容。</p></div>';
    }
}

生命周期方法

插件可以定义以下生命周期方法(可选):

class Plugin_my_plugin
{
    /**
     * 插件激活时执行
     * 可用于创建数据库表、初始化配置
     * @return bool|string 成功返回 true,失败返回错误信息字符串
     */
    public static function activate()
    {
        // 例如:创建自定义表
        // dbQuery("CREATE TABLE IF NOT EXISTS ...");
        return true;
    }

    /**
     * 插件停用时执行
     * 可用于清理数据(可选)
     * @return bool|string
     */
    public static function deactivate()
    {
        return true;
    }
}

配置页面接口

插件可通过 config()saveConfig() 方法提供后台配置页面。

config() 方法

返回配置页面的 HTML(包含 <form method="post"> 表单)。后台会自动在外层包裹面板和 CSRF 令牌。

class Plugin_my_plugin
{
    /**
     * 返回配置页面 HTML
     * 表单必须 method="post",后台会自动注入 _csrf 令牌
     */
    public static function config()
    {
        $current = getOption('my_plugin_setting', '默认值');
        return <<<HTML
<form method="post">
    <label>插件设置项</label>
    <input type="text" name="my_plugin_setting" value="{$current}" style="width:100%">
    <p style="margin-top:12px"><button class="btn" type="submit">保存配置</button></p>
</form>
HTML;
    }

    /**
     * 保存配置
     * @param array $post $_POST 数据
     * @return bool|string 成功返回 true,失败返回错误信息
     */
    public static function saveConfig($post)
    {
        if (isset($post['my_plugin_setting'])) {
            setOption('my_plugin_setting', trim($post['my_plugin_setting']));
        }
        return true;
    }
}

后台通过 admin/plugin-config.php?dir=your-plugin 访问配置页面。如果插件类有 config() 方法,插件管理页会显示"配置"按钮。

可用的核心函数

插件中可以直接调用 RyeBlog 的核心函数(定义在 inc/functions.php 中):

数据库

dbQuery($sql, $params)   // 执行写操作(INSERT/UPDATE/DELETE)
dbAll($sql, $params)     // 查询多行,返回数组
dbOne($sql, $params)     // 查询单行,返回数组或 null
dbInsert($sql, $params)  // 插入并返回 lastInsertId
所有 SQL 必须使用 PDO 预处理语句(? 占位符 + 参数绑定),禁止拼接 SQL。

选项 / 配置

getOption($key, $default = '')   // 读取站点配置
setOption($key, $value)          // 写入站点配置

URL 与品牌

siteTitle()        // 站点名称
siteSlogan()       // 站点标语
siteUrl()          // 站点 URL
homeUrl()          // 首页 URL
baseUrl($path)     // 拼接站点基础路径
postUrl($post)     // 文章 URL
pageUrl($pg)       // 独立页面 URL
categoryUrl($cat)  // 分类 URL
tagUrl($tag)       // 标签 URL

内容渲染

renderContent($content, $format)           // 渲染 Markdown/HTML 内容
renderContentWithToc($content, $format)    // 渲染并提取目录(返回 ['html'=>..., 'toc'=>[...]])
excerptOf($content, $format, $len)         // 提取摘要
postExcerpt($post, $len)                   // 文章摘要

文件上传 / 附件

getUploadRelDir()                   // 获取上传相对目录(usr/uploads/YYYYmm/)
ensureUploadDir($rel)               // 确保上传目录存在
makeUniqueFilename($original)       // 生成唯一文件名
sanitizeFilename($name)             // 文件名消毒
scanContentForImages($content, $format)           // 扫描正文中的本地上传图片 URL
scanContentForUsedAttachments($content, $format)   // 扫描正文所有附件引用
scanRemoteImages($content, $format)                // 扫描正文中的远程图片 URL
downloadRemoteFile($url, $destDir, $filename)      // 下载远程文件
registerAttachmentRecord($rel, $filename, $size, $mime, $postId)  // 注册附件记录
cleanupUnusedAttachments($postId, $usedKeys)        // 清理未引用附件

用户 / 鉴权

isLoggedIn()       // 前台用户是否登录
currentUser()      // 获取当前登录用户信息
isAdmin()          // 是否管理员
currentAdmin()     // 获取当前管理员信息
requireAdmin()     // 要求管理员登录(否则跳转)
requireUser()      // 要求用户登录(否则跳转)

安全

esc($val)          // HTML 转义输出
csrfToken()        // 获取 CSRF 令牌
checkCsrf()        // 验证 POST 中的 CSRF 令牌

工具

slugify($text)           // 生成 URL slug
formatDate($datetime, $format)  // 格式化日期
makeExcerpt($html, $len)        // 生成摘要
rrmdir($dir)                    // 递归删除目录
extractZip($tmpFile, $destDir)  // 解压 ZIP 文件
mkDirRecursive($path)           // 递归创建目录

代码规范

  1. 类名Plugin_{目录名},目录名仅使用字母、数字、下划线。
  2. 方法:钩子方法必须为 public static
  3. 安全:所有输出到 HTML 的内容必须使用 esc() 转义,防止 XSS。
  4. 数据库:所有 SQL 必须使用 PDO 预处理语句(? 占位符 + 参数绑定),禁止拼接 SQL。
  5. 命名空间:插件不应声明命名空间,保持全局函数/类可直接调用。
  6. 兼容性:插件应兼容 PHP 7.4+,不依赖非标准扩展。
  7. CSRF:配置表单提交时,后台已自动注入 _csrf 字段,saveConfig() 内可通过 checkCsrf() 验证。

ZIP 打包规范

  1. 将插件目录打包为 ZIP,ZIP 内应有一个顶层目录(即插件名)。
  2. 顶层目录内必须包含 Plugin.php
  3. 示例结构:

`` my-plugin.zip └── my-plugin/ ├── Plugin.php ├── lang/ │ └── en.php (可选:英文 UI 词典) └── assets/ ``

  1. 用户在后台「插件管理 → 上传安装」选择 ZIP 文件即可安装。

插件双语规范(i18n)

RyeBlog 双语架构(v4):english-admin 插件启用 ⇔ 双语模式——中文站 = 根目录(无前缀,URL 永不变),英文站 = /en 前缀;未启用 = 纯中文博客(无 /en、无切换器、纯中文库)。插件参与双语需遵守:

1. 插件 UI 双语(词典)

2. 插件内容双语(插件自管内容表)

3. 双语发布检查清单

□ 插件 UI(含前台钩子输出)全部中文经 __() 包裹?
□ usr/plugins/<dir>/lang/en.php 词典齐全(含前台 UI 词条)?
□ 插件自定义内容表按 *_en 双列 + L() 回退?
□ 插件表英文列已加入 english-admin::$enCols(开/关自动装/清)?
□ 后台表单英文输入仅在 bilingualEnabled() 时渲染与保存?
□ 停用 english-admin 后:英文列清理、无残留、纯中文站零报错?

参考实现:

完整示例

参见 usr/plugins/example/Plugin.php —— 文末版权声明插件(演示 articleFooter 钩子 + config()/saveConfig() 配置接口 + 占位符变量 + 颜色配色编辑 + 实时预览)。 参见 usr/plugins/data-import/Plugin.php —— 数据导入导出插件 v2.0(支持 WordPress/Typecho XML、SQL 导入;RyeBlog XML/SQL 导出;自动下载远程图片;set_time_limit(0) 防超时)。

发布插件

  1. 将插件目录打包为 ZIP。
  2. 用户在后台「插件管理」页面上传 ZIP 安装,或在后台启用。
  3. 也可手动解压到 usr/plugins/ 目录后启用。