Sndow CMS 插件开发 Skill 介绍

Sndow CMS 插件开发 Skill:从目录结构到 API 扩展的完整指南

插件是扩展 Sndow CMS 功能的重要方式。无论是文章目录、图片本地化、SEO 工具,还是后台增强和开放 API,都可以通过插件完成。

为了让开发者更准确地使用系统接口,Sndow CMS 提供了专门的插件开发 Skill,帮助开发者按照当前版本的真实结构编写插件。

什么是 Sndow CMS 插件开发 Skill?

Sndow CMS 插件开发 Skill 是面向插件开发、维护和排错的开发规范,适用于:

sd_content/plugins/

目录下的插件项目。

它主要解决以下问题:

  • 插件主文件应该放在哪里
  • 插件如何注册后台菜单
  • 插件安装、停用和卸载有什么区别
  • 插件设置应该保存在哪里
  • 如何正确处理权限和 CSRF
  • 如何调用模型和数据库
  • 如何扩展开放 API
  • 如何使用全局上传函数
  • 如何注册侧边栏小工具

推荐的插件目录结构

一个常见的插件目录如下:

sd_content/plugins/my-plugin/
├── my-plugin.php
├── admin-settings.php
├── options.php
├── includes/
└── assets/
    ├── css/
    └── js/

插件主文件建议使用与目录相同的名称:

my-plugin/my-plugin.php

这样系统在扫描插件时可以更准确地识别主文件。

正确编写插件头部信息

插件主文件顶部需要包含标准注释:

<?php
/**
 * Plugin Name: 我的插件
 * Plugin URI: https://www.sndow.com/index/store/detail/id/31.html
 * Description: 插件功能说明
 * Author: 作者或团队
 * Author URI: https://www.sndow.com/index/store/author/id/1.html
 * Version: 1.0.0
 */
if (!defined('SD_PATH')) exit;

其中:

  • Plugin Name 是后台显示的插件名称
  • Plugin URI 是插件详情页地址
  • Author URI 是作者主页地址
  • Version 是当前插件版本
  • Description 是插件功能说明

发布插件时,URI 应填写真实的官网地址,不要复制其他应用的地址,也不要使用不完整的域名或测试链接。

插件生命周期函数必须带前缀

Sndow CMS 当前推荐使用带插件 slug 前缀的生命周期函数。

例如插件目录为:

sd-toc

生命周期函数应写成:

function sd_toc_plugin_install() {
    // 插件启用时执行
}

function sd_toc_plugin_uninstall() {
    // 插件从后台删除时执行
}

function sd_toc_plugin_upgrade() {
    // 插件升级时执行
}

旧版的:

function plugin_install() {}
function plugin_uninstall() {}
function plugin_upgrade() {}

仅用于兼容旧插件,不建议新插件继续使用。通用函数名容易和其他插件冲突,也可能产生 Cannot redeclare 错误。

需要注意的是,停用插件不会自动执行卸载函数。停用时系统触发的是:

do_action('deactivate_sd_toc');

插件卸载函数只应该处理插件删除时的清理工作。

使用 sd_add_admin_menu 添加后台入口

插件可以通过 sd_add_admin_menu() 添加后台菜单:

sd_add_admin_menu(
    '文章目录',
    __FILE__,
    'admin-settings',
    'bi-list-ul',
    'manage_settings',
    'extension'
);

参数说明:

  • 第一个参数是菜单名称
  • 第二个参数通常使用 __FILE__
  • 第三个参数是插件后台页面文件名
  • 第四个参数是 Bootstrap Icons 图标
  • 第五个参数是访问所需权限
  • 第六个参数可以设置为 extension

插件后台页面通常位于:

sd_content/plugins/my-plugin/admin-settings.php

访问地址由系统统一处理,不需要插件自行编写后台路由。

后台设置页必须处理权限和 CSRF

插件后台页面不能只依赖菜单权限,还需要在页面内部再次检查权限:

global $auth;

if (!$auth->can('manage_settings')) {
    sd_die('权限不足');
}

处理表单提交时,需要验证 CSRF:

if (Request::isPost()) {
    sd_csrf_check();

    $title = Request::post('title', '');

    sd_set_config(
        'my_plugin',
        'title',
        $title
    );
}

表单中加入:

<?php sd_csrf_field(); ?>

如果是 AJAX 请求,可以使用:

sd_json_success('保存成功');

或者:

sd_json_error('保存失败');

这样可以保持插件和 Sndow CMS 后台的提示和返回格式一致。

使用插件配置和 Meta 存储数据

插件自身的配置,推荐使用 sd_config()sd_set_config()

$enabled = sd_config('my_plugin', 'enabled', '0');

sd_set_config(
    'my_plugin',
    'enabled',
    '1'
);

如果需要保存数组,可以自行编码为 JSON:

$settings = [
    'enabled' => true,
    'title' => '示例标题',
];

sd_set_config(
    'my_plugin',
    'settings',
    json_encode($settings, JSON_UNESCAPED_UNICODE)
);

文章、页面和用户扩展数据,则可以使用 Meta:

update_post_meta($post_id, 'my_plugin_data', [
    'score' => 10,
]);

$data = get_post_meta($post_id, 'my_plugin_data', true);

插件不应该自行修改 active_plugins,插件启用和停用状态应由系统后台统一维护。

优先使用系统模型和数据库接口

Sndow CMS 提供了文章、分类、标签、评论、用户、菜单、附件等模型。

global $post, $category, $tag, $comment, $user;

$item = $post->get($post_id);

$posts = $post->getAll(
    "post_status = ? AND post_type = ?",
    ['publish', 'post'],
    'post_date DESC',
    10,
    0
);

$total = $post->getCount(
    "post_type = ?",
    ['post']
);

只有在模型无法满足需求时,才直接使用 $db:

global $db;

$prefix = $db->getPrefix();

$row = $db->get(
    "SELECT * FROM {$prefix}my_plugin_items WHERE item_id = ?",
    [$item_id]
);

数据库操作必须使用参数绑定,不能拼接用户输入。

使用全局上传函数

Sndow CMS 提供了统一的 sd_upload_file()

$result = sd_upload_file($_FILES['file'], [
    'allowed_exts' => ['jpg', 'png', 'mp4'],
    'allowed_mimes' => ['image/jpeg', 'image/png', 'video/mp4'],
    'max_size_mb' => 20,
]);

if (!$result['success']) {
    sd_json_error($result['message']);
}

$file_url = $result['url'];
$attachment_id = $result['id'];

该函数会统一处理:

  • 文件扩展名
  • 实际 MIME 类型
  • 后台允许的文件类型
  • 最大上传大小
  • 上传目录
  • 附件记录
  • 图片缩略图

插件不应该只根据文件名后缀判断文件类型,也不应该重复实现上传目录和附件写入逻辑。

注册插件小工具

插件可以注册自己的小工具类型:

sd_register_widget_type('my_plugin_notice', [
    'name' => '插件提示',
    'icon' => 'bi-info-circle',
    'desc' => '显示插件提示信息',
    'render_callback' => function ($config) {
        echo '<div>' .
            htmlspecialchars($config['text'] ?? '', ENT_QUOTES, 'UTF-8') .
            '</div>';
    },
]);

小工具类型建议使用插件前缀,例如:

my_plugin_notice
sd_toc_widget
image_localizer_status

这样可以避免和主题或其他插件的小工具类型重名。

扩展开放 API

插件可以使用 api_custom_action 扩展自定义 API:

add_filter('api_custom_action', function ($handled, $action, $api) {
    if ($action !== 'my_plugin/status') {
        return $handled;
    }

    $api->success([
        'enabled' => true,
    ]);

    return true;
}, 10, 3);

插件还可以通过以下过滤器扩展系统接口:

add_filter('api_home_data', function ($data) {
    $data['my_plugin'] = [
        'enabled' => true,
    ];

    return $data;
});

API 的总开关、Token、请求限流、最大请求数量和跨域来源由后台系统设置控制。插件不应该绕过系统已有的 API 安全检查。

插件资源加载

插件 CSS 和 JavaScript 可以使用系统资源函数:

sd_enqueue_style(
    'my-plugin-style',
    sd_plugin_url(__FILE__, 'assets/css/plugin.css'),
    [],
    '1.0.0'
);

sd_enqueue_script(
    'my-plugin-script',
    sd_plugin_url(__FILE__, 'assets/js/plugin.js'),
    [],
    '1.0.0',
    true
);

插件文件路径可以使用:

$path = sd_plugin_path(__FILE__, 'data/cache.json');

这样可以避免硬编码服务器绝对路径,也方便插件在不同目录中运行。

插件发布前检查

插件打包前建议检查以下内容:

  • 主文件包含完整插件头部信息
  • ZIP 内只有一个顶层插件目录
  • 主文件名和目录名保持一致
  • 生命周期函数使用插件 slug 前缀
  • 后台页面有权限检查
  • 写操作包含 CSRF 验证
  • 数据库查询使用参数绑定
  • 上传功能使用 sd_upload_file()
  • 不修改系统的 active_plugins
  • 停用插件不会误删数据
  • 卸载只清理本插件自己的配置和数据
  • 没有调试输出、绝对路径和敏感 Token
  • 所有 PHP 文件通过 php -l 检查

总结

Sndow CMS 插件开发 Skill 将插件目录、生命周期、后台页面、配置存储、数据库、上传、小工具和 API 扩展整合在一起,为开发者提供了一套更加清晰的开发路径。

使用这套 Skill,可以减少插件开发过程中因生命周期函数冲突、权限遗漏、CSRF 缺失、数据库查询不规范和上传逻辑重复而产生的问题,让插件更容易维护,也更适合发布到 Sndow CMS 应用商店。

点击下载skill