子比主题开发文档
使用指南Codestar Framework主题扩展在线部署AI 功能推荐插件赞助打赏

插件制作指南

基于子比主题开发可独立安装、停用和复用的 WordPress 插件,覆盖入口文件、加载时机、CSF 设置、Hook、Ajax 与发布检查。

插件适合承载可以独立启用、停用、分发给多个站点复用的功能。和子主题相比,插件不负责替换整站模板,也不应该依赖某个站点的视觉结构;它更适合做支付规则、文章内容增强、后台设置、短代码、Ajax 接口、数据同步、通知等功能模块。

如果功能和当前站点强绑定,例如整站样式、模板覆盖、用户中心页面结构、支付页面模板改造,优先使用 子主题制作指南

什么时候写插件

场景推荐方式
功能需要单独安装、停用或分发插件
多个站点都要复用同一套功能插件
只需要接入 Hook、Ajax、短代码或后台设置插件
需要覆盖父主题模板文件子主题
样式和页面结构完全服务于当前站点子主题

插件的边界要清楚:它可以调用子比主题函数、读取主题设置、挂载主题 Hook,但不要把父主题模板复制进插件里长期维护。模板覆盖交给子主题,插件保留业务逻辑。

基础目录

一个可维护的插件至少分三层:

zibll-site-tools/
├── index.php
└── includes/
    ├── admin-options.php
    └── functions.php

index.php 负责声明插件、定义常量、检查运行环境和加载文件;includes/admin-options.php 负责 CSF 设置页;includes/functions.php 负责业务 Hook、过滤器、Ajax 和短代码。

复杂插件可以继续拆分:

zibll-site-tools/
├── index.php
├── assets/
│   ├── css/
│   └── js/
└── includes/
    ├── admin-options.php
    ├── ajax.php
    ├── functions.php
    ├── shortcode.php
    └── upgrade.php

入口文件

入口文件只做初始化,不要堆业务逻辑:

<?php
/*
 * Description: Site tools for Zibll.
 * Version: 1.0.0
 * Author: Your Name
 */

if (!defined('ABSPATH')) {
    exit;
}

define('ZIBLL_SITE_TOOLS_PATH', plugin_dir_path(__FILE__));
define('ZIBLL_SITE_TOOLS_URL', plugins_url('', __FILE__));

function zibll_site_tools_get_option($option = '', $default = null)
{
    static $options = null;

    if (null === $options) {
        $options = get_option('zibll_site_tools');
    }

    return isset($options[$option]) ? $options[$option] : $default;
}

function zibll_site_tools_init()
{
    $require_once = array(
        'includes/admin-options.php',
        'includes/functions.php',
    );

    foreach ($require_once as $require) {
        require_once ZIBLL_SITE_TOOLS_PATH . $require;
    }
}
add_action('zib_require_end', 'zibll_site_tools_init');

加载时机建议挂到 zib_require_end。这样子比主题的函数、CSF 类、支付模块和常用封装已经加载完成,插件可以安全调用主题能力。

入口头信息是 WordPress 识别插件的关键,但文档和示例里不需要把业务名写死。发布给用户前至少包含描述、版本、作者、最低 WordPress/PHP 版本说明,并在 README 里写清楚依赖子比主题:

<?php
/*
 * Description: Site tools for Zibll.
 * Version: 1.0.0
 * Requires at least: 5.2
 * Requires PHP: 7.0
 */

if (!defined('ABSPATH')) {
    exit;
}

如果插件依赖子比主题函数,入口不要在文件加载瞬间直接调用主题函数。先定义常量和辅助函数,再把业务文件挂到 zib_require_end,这样能避开主题尚未加载、CSF 类不存在、支付模块未初始化等问题。

加载生命周期

子比主题的 functions.php 会先载入 inc/inc.php,再由 zib_require() 依次加载依赖、主题函数、Codestar Framework、小工具、OAuth、Zibpay、Ajax 基础函数和 CSF 扩展类,最后触发:

do_action('zib_require_end');

这意味着插件可以按依赖强度选择加载点:

加载点适合做什么不适合做什么
插件文件被 WordPress 加载时定义常量、轻量辅助函数、注册激活/卸载钩子直接调用 _pz()zibpay::*CSF 或主题模板函数
plugins_loaded读取插件自身版本、加载翻译、准备兼容层依赖子比主题业务模块
zib_require_end加载依赖主题能力的业务文件、注册主题 Hook、注册 CSF 设置输出页面 HTML 或执行慢任务
init注册短代码、查询变量、自定义路由、轻量 rewrite创建大量设置字段或跑迁移
wp_enqueue_scripts按页面加载前台资源全站无条件加载大脚本
admin_init注册后台设置、处理后台环境判断执行前台业务逻辑

推荐把“入口”和“业务”拆开:

function zibll_site_tools_boot()
{
    if (!defined('ZIB_TEMPLATE_DIRECTORY_URI')) {
        return;
    }

    require_once ZIBLL_SITE_TOOLS_PATH . 'includes/admin-options.php';
    require_once ZIBLL_SITE_TOOLS_PATH . 'includes/functions.php';
    require_once ZIBLL_SITE_TOOLS_PATH . 'includes/ajax.php';
}
add_action('zib_require_end', 'zibll_site_tools_boot');

如果插件需要在非子比主题下给管理员提示,不要让前台报致命错误:

function zibll_site_tools_admin_notice()
{
    if (defined('ZIB_TEMPLATE_DIRECTORY_URI')) {
        return;
    }

    echo '<div class="notice notice-warning"><p>' . esc_html__('当前扩展需要启用子比主题后才能工作。', 'zib_language') . '</p></div>';
}
add_action('admin_notices', 'zibll_site_tools_admin_notice');

依赖检测只用于阻止业务加载,不要在用户前台 wp_die()。站点切换主题、临时排查问题或后台禁用模块时,插件应该保持可进入后台、可停用、可修复。

CSF 设置页

插件设置页可以复用子比主题随带的 Codestar Framework。关键是保持独立 option key,不要写入 zibll_options

<?php
if (!defined('ABSPATH')) {
    exit;
}

if (class_exists('CSF')) {
    $prefix = 'zibll_site_tools';

    CSF::createOptions($prefix, array(
        'menu_title'      => '站点工具',
        'menu_slug'       => 'zibll_site_tools',
        'framework_title' => '站点工具 <small>v1.0.0</small>',
        'theme'           => 'light',
    ));

    CSF::createSection($prefix, array(
        'id'     => 'base',
        'title'  => '基础设置',
        'icon'   => 'fa fa-cog',
        'fields' => array(
            array(
                'id'      => 'enabled',
                'type'    => 'switcher',
                'title'   => '启用功能',
                'default' => false,
            ),
            array(
                'id'         => 'notice_text',
                'type'       => 'text',
                'title'      => '提示文字',
                'dependency' => array('enabled', '==', '1'),
                'default'    => '',
            ),
        ),
    ));
}

字段命名使用插件前缀或独立 option key,避免和主题字段冲突。读取时统一走辅助函数,例如 zibll_site_tools_get_option(),不要在业务代码里反复 get_option()

复杂设置页建议做“只在需要时构建”。CSF 字段很多时,如果每个后台 Ajax 都完整创建设置字段,容易拖慢后台,也可能让默认值保存时机变得混乱:

function zibll_site_tools_create_options()
{
    if (!is_admin() || !class_exists('CSF')) {
        return;
    }

    $prefix = 'zibll_site_tools';
    $is_self_page = !empty($_GET['page']) && $prefix === sanitize_key($_GET['page']);
    $is_csf_ajax  = wp_doing_ajax()
        && !empty($_POST['action'])
        && false !== strpos(sanitize_text_field(wp_unslash($_POST['action'])), 'csf_' . $prefix);

    CSF::createOptions($prefix, array(
        'menu_title'    => '站点工具',
        'menu_slug'     => $prefix,
        'save_defaults' => $is_self_page || $is_csf_ajax,
        'theme'         => 'light',
    ));

    CSF::createSection($prefix, array(
        'id'    => 'base',
        'title' => '基础设置',
        'icon'  => 'fa fa-cog',
    ));

    if (!$is_self_page && !$is_csf_ajax) {
        return;
    }

    CSF::createSection($prefix, array(
        'parent' => 'base',
        'title'  => '功能开关',
        'fields' => array(
            array(
                'id'      => 'enabled',
                'type'    => 'switcher',
                'title'   => '启用功能',
                'default' => false,
            ),
        ),
    ));
}
add_action('admin_init', 'zibll_site_tools_create_options');

这种写法的重点是:先创建菜单壳,保证后台左侧能显示;再按当前页面或 CSF 自身 Ajax 决定是否创建完整字段。

Hook 写法

业务文件保持子比主题常见写法:命名函数、明确优先级、按场景返回原值。

<?php
if (!defined('ABSPATH')) {
    exit;
}

function zibll_site_tools_add_article_notice($content)
{
    if (!is_single() || !zibll_site_tools_get_option('enabled')) {
        return $content;
    }

    $notice = zibll_site_tools_get_option('notice_text', '');
    if (!$notice) {
        return $content;
    }

    $content .= '<div class="muted-2-color em09 mt10">' . esc_html($notice) . '</div>';

    return $content;
}
add_filter('the_content', 'zibll_site_tools_add_article_notice', 99);

过滤器要尽量保留原始数据结构,不能处理时直接返回原值。动作函数要先判断开关、页面类型、用户权限和必要参数。

支付或商城规则适合写成过滤器。比如限制某些支付场景使用余额支付,应只改布尔结果,不直接改订单或支付单:

function zibll_site_tools_filter_balance_pay($allow, $pay_type)
{
    $disabled_types = (array) zibll_site_tools_get_option('disabled_balance_pay_types', array());

    if (in_array((string) $pay_type, $disabled_types, true)) {
        return false;
    }

    return $allow;
}
add_filter('zibpay_is_allow_balance_pay', 'zibll_site_tools_filter_balance_pay', 10, 2);

这类扩展要把“支付类型枚举”和“是否允许”分开,避免在过滤器里创建订单、修改余额或输出页面。

Ajax 接口

Ajax 接口要保持 WordPress 权限模型和子比主题返回风格:

function zibll_site_tools_ajax_save_demo()
{
    if (!is_user_logged_in()) {
        zib_send_json_error('请先登录');
    }

    $user_id = get_current_user_id();
    $value   = isset($_POST['value']) ? sanitize_text_field(wp_unslash($_POST['value'])) : '';

    if (!$value) {
        zib_send_json_error('参数错误');
    }

    update_user_meta($user_id, 'zibll_site_tools_demo', $value);

    zib_send_json_success(array(
        'msg'   => '保存成功',
        'value' => $value,
    ));
}
add_action('wp_ajax_zibll_site_tools_save_demo', 'zibll_site_tools_ajax_save_demo');

需要游客访问时再增加 wp_ajax_nopriv_。只给已登录用户使用的接口不要开放游客入口。

前台按钮如果要复用主题的 Ajax 交互,action 名称和本地化语言也要对齐。子比主题会用 zib_is_admin_context() 判断 Ajax 是否属于前台请求,并提供两个过滤器:

function zibll_site_tools_frontend_ajax_actions($actions)
{
    $actions[] = 'zibll_site_tools_save_demo';
    return $actions;
}
add_filter('zib_locale_frontend_ajax_actions', 'zibll_site_tools_frontend_ajax_actions');

如果你的前台 action 有统一前缀,也可以扩展前缀列表:

function zibll_site_tools_frontend_ajax_prefixes($prefixes)
{
    $prefixes[] = 'zibll_site_tools_';
    return $prefixes;
}
add_filter('zib_locale_frontend_ajax_prefixes', 'zibll_site_tools_frontend_ajax_prefixes');

这样后台语言和前台语言分离时,插件前台 Ajax 不会被误判为后台请求。涉及支付、验证码、用户中心的动作尤其要注意这一点。

公开给游客的 Ajax 要额外做频率、nonce 或人机验证。子比主题里已经有 zib_ajax_man_machine_verification()zib_ajax_verify_nonce()zib_is_error_frequency_limit() 这类基础函数,插件可以复用,但要保证调用发生在 zib_require_end 之后。

资源加载

前台资源按页面条件加载,避免全站无意义注入:

function zibll_site_tools_enqueue_scripts()
{
    if (!zibll_site_tools_get_option('enabled')) {
        return;
    }

    wp_enqueue_style(
        'zibll-site-tools',
        ZIBLL_SITE_TOOLS_URL . '/assets/css/site-tools.css',
        array(),
        '1.0.0'
    );
}
add_action('wp_enqueue_scripts', 'zibll_site_tools_enqueue_scripts');

后台资源使用 admin_enqueue_scripts,并根据 $hook_suffix 或当前页面参数限制范围。

后台自定义 CSS 或代码编辑字段要特别小心。保存时先过滤,输出时再按场景转义:

function zibll_site_tools_admin_css()
{
    $css = zibll_site_tools_get_option('admin_css', '');

    if (!$css || !current_user_can('manage_options')) {
        return;
    }

    echo '<style>' . wp_strip_all_tags($css) . '</style>';
}
add_action('admin_head', 'zibll_site_tools_admin_css', 99);

如果允许用户输入 HTML,必须使用 wp_kses_post() 或更严格的白名单。不要把后台设置里的任意内容无过滤输出到前台。

升级和卸载

插件只要写入自定义 option、user meta、post meta、订单 meta 或自建表,就要提前设计升级和清理策略。推荐用独立版本号记录迁移状态:

function zibll_site_tools_maybe_upgrade()
{
    $version = get_option('zibll_site_tools_version', '0');

    if (version_compare($version, '1.1.0', '<')) {
        zibll_site_tools_upgrade_110();
        update_option('zibll_site_tools_version', '1.1.0');
    }
}
add_action('admin_init', 'zibll_site_tools_maybe_upgrade');

迁移函数要小步、幂等、可重复执行:

function zibll_site_tools_upgrade_110()
{
    $options = get_option('zibll_site_tools', array());

    if (!isset($options['enabled'])) {
        $options['enabled'] = false;
    }

    update_option('zibll_site_tools', $options);
}

不要把历史数据迁移写在每次前台访问里。数据量大时,后台分批处理,记录游标和完成时间;涉及 Zibpay 订单、用户积分、余额、分佣、提现的数据,只能通过主题已有函数和订单状态流核对,不能直接改资产字段。

卸载可以分两层:停用插件时只移除运行时任务,不删除用户数据;真正卸载时再根据后台开关清理 option 和插件自有 meta。涉及订单、余额、积分、下载权限和审计日志的数据,默认保留更稳。

function zibll_site_tools_deactivate()
{
    wp_clear_scheduled_hook('zibll_site_tools_daily_event');
}
register_deactivation_hook(__FILE__, 'zibll_site_tools_deactivate');

发布检查

发布前至少检查:

检查项要求
入口文件有插件头信息、ABSPATH 防直访、常量定义
加载时机依赖子比主题能力的代码挂到 zib_require_end 之后
命名函数、option key、action 名称有唯一前缀
设置使用独立 option key,不写入主题主配置
Ajax权限、参数、nonce 或业务校验齐全
输出HTML、属性、URL、文本按场景转义
资源只在需要的页面加载 CSS 和 JS
升级迁移按版本执行,幂等、可回滚、可跳过已完成数据
卸载停用不删数据,真正卸载时再按配置清理插件自有数据
兼容未启用子比主题时后台可提示,前台不应致命错误

插件越独立,后期越容易维护。需要和主题深度结合时,也要先让插件负责逻辑,再把模板交给子主题或主题已有 Hook。

On this page