WordPress函数:register_setting 注册设置选项

编辑文章

简介

register_setting 是 WordPress Settings API 的核心函数,用于声明一个可被后台设置表单安全更新和管理的配置选项。它将你的插件选项与WordPress的配置系统集成,自动处理数据验证、权限检查和数据库存储。

语法

register_setting 函数定义于 wp-admin/includes/plugin.php 文件中,但在 wp-admin/options.php 中被主要使用。它在 admin_init 动作钩子中被调用,负责向全局 $new_whitelist_options 数组注册设置选项,这是处理表单提交时的核心安全机制之一。

函数签名(WordPress 4.7+ 版本):

register_setting(
    string $option_group,
    string $option_name,
    array $args = array()
);

参数与返回值说明

参数名 类型 必选/可选 默认值 说明
$option_group 字符串 必选 设置所属的组,必须与 settings_fields() 调用中的参数一致。
$option_name 字符串 必选 在数据库 wp_options 表中存储的选项名称。
$args 数组 可选 array() 配置选项的详细参数数组(见下方详述)。

$args 数组支持的键值:

键名 类型 默认值 说明
type 字符串 'string' 选项的数据类型(string, boolean, integer, number, array, object)。
description 字符串 '' 选项的简短描述,可通过 REST API 获取。
sanitize_callback 可调用函数 null 用于清理和验证用户输入的回调函数。这是安全性的关键
show_in_rest 布尔值/数组 false 是否通过 WordPress REST API 公开此选项。
default 混合 选项的默认值。

返回值:总是返回 null。此函数的主要作用是注册设置,而非返回值。

用法

基础用法

最常见的情况是为你的插件注册一个简单的文本选项,并在设置页面上提供表单字段。以下是一个完整示例,展示如何将 register_settingadd_settings_field 结合使用。

<?php
/**
 * Plugin Name: 我的插件设置注册示例
 */

// 防止直接访问
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

// 步骤1: 在 admin_menu 钩子中添加设置页面(可参考 add_options_page 教程)
add_action( 'admin_menu', 'my_plugin_add_menu' );
function my_plugin_add_menu() {
    add_options_page(
        '我的插件设置',
        '我的插件',
        'manage_options',
        'my-plugin-settings',
        'my_plugin_render_settings_page'
    );
}

// 步骤2: 在 admin_init 钩子中注册设置、字段和章节
add_action( 'admin_init', 'my_plugin_register_settings' );
function my_plugin_register_settings() {
    // 注册一个设置选项
    register_setting(
        'my_plugin_settings_group', // 选项组名,必须唯一
        'my_plugin_api_key',        // 选项名,即 wp_options 表中的 option_name
        array(
            'type'              => 'string',
            'description'       => '用于连接第三方服务的API密钥',
            // 最重要的部分:清理回调函数
            'sanitize_callback' => 'my_plugin_sanitize_api_key',
            'default'           => '',
        )
    );

    // 添加一个设置章节(可选,但推荐用于组织)
    add_settings_section(
        'my_plugin_main_section',
        '主要设置',
        'my_plugin_section_callback',
        'my-plugin-settings' // 必须与 add_options_page 的 $menu_slug 一致
    );

    // 为上面的设置添加一个字段
    add_settings_field(
        'my_plugin_api_key_field',
        'API 密钥',
        'my_plugin_api_key_field_callback',
        'my-plugin-settings', // 页面
        'my_plugin_main_section' // 章节
    );
}

// 清理回调函数:负责验证和清理输入
function my_plugin_sanitize_api_key( $input ) {
    // 去除首尾空格
    $cleaned = trim( $input );

    // 示例验证:假设API密钥必须是32位十六进制字符串
    if ( ! empty( $cleaned ) && ! preg_match( '/^[a-fA-F0-9]{32}$/', $cleaned ) ) {
        // 如果验证失败,添加一个设置错误,并返回旧值
        add_settings_error(
            'my_plugin_api_key',
            'invalid_api_key',
            'API密钥必须是32位十六进制字符串。'
        );
        // 返回数据库中已存在的旧值,而不是无效的新值
        return get_option( 'my_plugin_api_key' );
    }

    return $cleaned; // 返回清理后的值
}

// 设置章节的回调函数(可以输出一些描述)
function my_plugin_section_callback() {
    echo '<p>请在此配置插件的主要设置。</p>';
}

// 设置字段的回调函数:输出表单HTML
function my_plugin_api_key_field_callback() {
    $option_value = get_option( 'my_plugin_api_key', '' ); // 获取当前值
    ?>
    <input type="text"
           id="my_plugin_api_key"
           name="my_plugin_api_key" <!-- 注意:name 属性必须等于选项名 -->
           value="<?php echo esc_attr( $option_value ); ?>"
           class="regular-text" />
    <p class="description">请输入从服务提供商处获取的32位API密钥。</p>
    <?php
}

// 步骤3: 渲染设置页面的回调函数
function my_plugin_render_settings_page() {
    ?>
    <div class="wrap">
        <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
        <!-- 必须使用 options.php 作为表单处理器 -->
        <form action="options.php" method="post">
            <?php
            // 输出必要的安全字段(nonce等),参数必须与 register_setting 的 $option_group 一致
            settings_fields( 'my_plugin_settings_group' );
            // 输出指定页面上的所有设置章节和字段
            do_settings_sections( 'my-plugin-settings' );
            // 输出提交按钮
            submit_button( '保存设置' );
            ?>
        </form>
    </div>
    <?php
}
?>

进阶用法

注册一个数组类型的选项,用于存储多个相关设置值,这比注册多个独立的选项更高效,也便于管理。

// 在 my_plugin_register_settings 函数中添加
register_setting(
    'my_plugin_settings_group',
    'my_plugin_social_links', // 这个选项将存储一个数组
    array(
        'type'              => 'array', // 指定类型为数组
        'sanitize_callback' => 'my_plugin_sanitize_social_links',
        'default'           => array(
            'twitter'  => '',
            'facebook' => '',
            'linkedin' => '',
        ),
    )
);

// 对应的清理回调函数
function my_plugin_sanitize_social_links( $input ) {
    $output = array();

    // 确保输入是一个数组
    if ( ! is_array( $input ) ) {
        return get_option( 'my_plugin_social_links' ); // 返回旧值
    }

    // 清理每个社交链接字段
    $allowed_keys = array( 'twitter', 'facebook', 'linkedin' );
    foreach ( $allowed_keys as $key ) {
        if ( isset( $input[ $key ] ) ) {
            // 清理URL
            $output[ $key ] = esc_url_raw( trim( $input[ $key ] ) );
        } else {
            $output[ $key ] = '';
        }
    }

    return $output;
}

// 为数组选项添加多个字段
add_settings_field(
    'my_plugin_twitter_field',
    'Twitter URL',
    'my_plugin_twitter_field_callback',
    'my-plugin-settings',
    'my_plugin_main_section'
);

function my_plugin_twitter_field_callback() {
    $options = get_option( 'my_plugin_social_links' );
    $value = isset( $options['twitter'] ) ? $options['twitter'] : '';
    ?>
    <input type="url"
           id="my_plugin_twitter"
           name="my_plugin_social_links[twitter]" <!-- 注意:name 使用数组语法 -->
           value="<?php echo esc_url( $value ); ?>"
           class="regular-text" />
    <?php
}
// ... 类似地为 facebook 和 linkedin 添加字段

易错点

  • $option_groupsettings_fields() 不匹配:这是最常见的错误。register_setting 的第一个参数必须与 settings_fields() 函数调用时的参数完全相同,否则表单无法正确提交,安全验证会失败。
  • 缺少或无效的 sanitize_callback:这是严重的安全漏洞。没有清理回调,用户输入将直接存入数据库。回调函数必须返回清理后的值,或通过 add_settings_error() 报告错误并返回旧值。
  • sanitize_callback 中直接使用 $_POST:清理回调函数的参数是已提交的、待清理的值,不是 $_POST 数组。直接访问 $_POST 会绕过 WordPress 的安全检查,并可能导致逻辑错误。
  • 选项名冲突:使用过于通用的选项名(如 api_key)可能与其他插件冲突。始终使用你的插件名称作为前缀(如 myplugin_api_key)。
  • 对数组选项处理不当:当选项类型是数组时,sanitize_callback 收到的 $input 可能不是数组(例如,如果所有字段都被清空)。你的清理函数必须能处理这种情况。
  • 忘记设置 default:对于新安装,如果选项不存在且用户从未保存,get_option 将返回 false。设置合理的 default 值可以确保你的代码始终有预期的数据结构。

最佳实践

安全第一:设计健壮的清理回调

清理回调不仅要去除无效字符,更要进行业务逻辑验证。例如,一个存储邮箱列表的选项,其清理函数应该:
1. 将字符串按逗号分割为数组
2. 遍历每个邮箱,使用 sanitize_email() 清理
3. 移除无效的邮箱地址
4. 限制数组的最大长度(防止滥用)
5. 返回清理后的数组

function my_plugin_sanitize_email_list( $input ) {
    if ( ! is_string( $input ) ) {
        return array();
    }

    $emails = explode( ',', $input );
    $cleaned_emails = array();

    foreach ( $emails as $email ) {
        $email = sanitize_email( trim( $email ) );
        if ( is_email( $email ) && count( $cleaned_emails ) < 50 ) { // 限制最多50个
            $cleaned_emails[] = $email;
        }
    }

    return array_unique( $cleaned_emails ); // 去重
}

利用 show_in_rest 与现代架构集成

从 WordPress 4.7 开始,register_setting 支持 show_in_rest 参数,允许你的设置通过 WordPress REST API 暴露。这对于创建基于JavaScript的现代管理界面(如使用React的Gutenberg区块或自定义管理面板)至关重要。

register_setting(
    'my_plugin_settings_group',
    'my_plugin_preferences',
    array(
        'type'              => 'object',
        'description'       => '用户界面偏好设置',
        'sanitize_callback' => 'my_plugin_sanitize_preferences',
        'default'           => array(
            'dark_mode'   => false,
            'items_per_page' => 20,
        ),
        // 通过 REST API 公开此选项
        'show_in_rest'      => array(
            'schema' => array(
                'type'       => 'object',
                'properties' => array(
                    'dark_mode' => array(
                        'type' => 'boolean',
                    ),
                    'items_per_page' => array(
                        'type'    => 'integer',
                        'minimum' => 5,
                        'maximum' => 100,
                    ),
                ),
            ),
        ),
    )
);

这样,你可以通过 GET /wp-json/wp/v2/settings 读取和 POST /wp-json/wp/v2/settings 更新这些设置(需要适当权限)。

组织代码:面向对象的封装

对于拥有多个设置的复杂插件,使用类来封装所有设置相关逻辑可以提高代码的可维护性和可读性。

class My_Plugin_Settings {
    private $option_group = 'my_plugin_settings_group';

    public function __construct() {
        add_action( 'admin_init', array( $this, 'register_all_settings' ) );
    }

    public function register_all_settings() {
        $this->register_api_settings();
        $this->register_ui_settings();
        // ... 注册更多设置
    }

    private function register_api_settings() {
        register_setting(
            $this->option_group,
            'my_plugin_api_key',
            array(
                'sanitize_callback' => array( $this, 'sanitize_api_key' ),
                'default'           => '',
            )
        );
        // 注册对应的字段...
    }

    public function sanitize_api_key( $input ) {
        // 清理逻辑
        return trim( $input );
    }

    // ... 其他方法
}
new My_Plugin_Settings();