WordPress函数:add_settings_field 添加设置字段

编辑文章

简介

add_settings_field 函数是 WordPress Settings API 的重要组成部分,用于在后台设置页面中定义一个具体的配置字段(如表单输入框、选择框等)。它负责将字段的HTML输出逻辑与底层的数据存储逻辑连接起来。

语法

add_settings_field 函数定义于 wp-admin/includes/template.php 文件中,它向全局 $wp_settings_fields 数组添加字段信息,这些信息随后由 do_settings_sectionsdo_settings_fields 函数读取并输出。

函数签名:

add_settings_field(
    string $id,
    string $title,
    callable $callback,
    string $page,
    string $section = 'default',
    array $args = array()
);

参数与返回值说明

参数名 类型 必选/可选 默认值 说明
$id 字符串 必选 字段的唯一标识符(HTML id属性的一部分)。
$title 字符串 必选 字段的标签文本,显示在字段左侧。
$callback 可调用函数 必选 输出字段HTML内容的回调函数。
$page 字符串 必选 字段所属的设置页面slug,必须与 add_options_page$menu_slug 一致。
$section 字符串 可选 'default' 字段所属的设置章节id,必须与 add_settings_section$id 一致。
$args 数组 可选 array() 传递给回调函数的额外参数数组。

返回值:
– 总是返回 null。此函数的作用是注册字段信息。

用法

基础用法

register_settingadd_settings_section 配合使用,创建一个完整的文本输入字段。

以下是一个完整示例,展示如何创建包含两个字段的设置页面:

<?php
/**
 * Plugin Name: 设置字段示例插件
 */

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

// 1. 添加设置页面
add_action( 'admin_menu', 'my_plugin_add_settings_page' );
function my_plugin_add_settings_page() {
    add_options_page(
        '我的插件设置',
        '我的插件',
        'manage_options',
        'my-plugin-settings',
        'my_plugin_render_settings_page'
    );
}

// 2. 注册设置和字段
add_action( 'admin_init', 'my_plugin_register_settings' );
function my_plugin_register_settings() {
    // 注册设置选项
    register_setting(
        'my_plugin_settings_group',
        'my_plugin_settings', // 使用一个数组存储所有设置
        array(
            'type'              => 'array',
            'sanitize_callback' => 'my_plugin_sanitize_settings',
            'default'           => array(
                'text_field'   => '',
                'select_field' => 'option1',
            ),
        )
    );

    // 添加设置章节
    add_settings_section(
        'my_plugin_main_section',
        '主要设置',
        'my_plugin_main_section_callback',
        'my-plugin-settings'
    );

    // 添加文本字段
    add_settings_field(
        'my_plugin_text_field', // 字段ID
        '文本字段',             // 字段标题
        'my_plugin_text_field_callback', // 输出字段HTML的回调函数
        'my-plugin-settings',   // 页面slug
        'my_plugin_main_section', // 章节ID
        array(                  // 传递给回调函数的额外参数
            'label_for' => 'my_plugin_text_field', // 让标签关联到字段
            'description' => '这是一个示例文本字段',
        )
    );

    // 添加选择字段
    add_settings_field(
        'my_plugin_select_field',
        '选择字段',
        'my_plugin_select_field_callback',
        'my-plugin-settings',
        'my_plugin_main_section',
        array(
            'label_for' => 'my_plugin_select_field',
            'description' => '请选择一个选项',
        )
    );
}

// 3. 清理回调函数
function my_plugin_sanitize_settings( $input ) {
    $output = array();

    // 清理文本字段
    if ( isset( $input['text_field'] ) ) {
        $output['text_field'] = sanitize_text_field( $input['text_field'] );
    }

    // 验证选择字段
    $valid_options = array( 'option1', 'option2', 'option3' );
    if ( isset( $input['select_field'] ) && in_array( $input['select_field'], $valid_options, true ) ) {
        $output['select_field'] = $input['select_field'];
    } else {
        $output['select_field'] = 'option1'; // 默认值
    }

    return $output;
}

// 4. 章节回调函数
function my_plugin_main_section_callback() {
    echo '<p>这里可以放置章节的描述信息。</p>';
}

// 5. 文本字段回调函数
function my_plugin_text_field_callback( $args ) {
    $options = get_option( 'my_plugin_settings' );
    $value = isset( $options['text_field'] ) ? $options['text_field'] : '';
    ?>
    <input type="text"
           id="<?php echo esc_attr( $args['label_for'] ); ?>"
           name="my_plugin_settings[text_field]"
           value="<?php echo esc_attr( $value ); ?>"
           class="regular-text" />
    <?php if ( ! empty( $args['description'] ) ) : ?>
        <p class="description"><?php echo esc_html( $args['description'] ); ?></p>
    <?php endif; ?>
    <?php
}

// 6. 选择字段回调函数
function my_plugin_select_field_callback( $args ) {
    $options = get_option( 'my_plugin_settings' );
    $current_value = isset( $options['select_field'] ) ? $options['select_field'] : 'option1';
    ?>
    <select id="<?php echo esc_attr( $args['label_for'] ); ?>"
            name="my_plugin_settings[select_field]">
        <option value="option1" <?php selected( $current_value, 'option1' ); ?>>选项一</option>
        <option value="option2" <?php selected( $current_value, 'option2' ); ?>>选项二</option>
        <option value="option3" <?php selected( $current_value, 'option3' ); ?>>选项三</option>
    </select>
    <?php if ( ! empty( $args['description'] ) ) : ?>
        <p class="description"><?php echo esc_html( $args['description'] ); ?></p>
    <?php endif; ?>
    <?php
}

// 7. 渲染设置页面
function my_plugin_render_settings_page() {
    ?>
    <div class="wrap">
        <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
        <form action="options.php" method="post">
            <?php
            settings_fields( 'my_plugin_settings_group' );
            do_settings_sections( 'my-plugin-settings' );
            submit_button( '保存设置' );
            ?>
        </form>
    </div>
    <?php
}
?>

进阶用法

创建可重复添加的动态字段组,比如允许用户添加多个自定义链接。

// 在注册设置时,使用数组存储多个链接
register_setting(
    'my_plugin_settings_group',
    'my_plugin_custom_links',
    array(
        'type'              => 'array',
        'sanitize_callback' => 'my_plugin_sanitize_custom_links',
        'default'           => array(),
    )
);

// 动态字段组的回调函数
function my_plugin_links_field_callback() {
    $links = get_option( 'my_plugin_custom_links', array() );

    // 添加一个空项用于JavaScript克隆
    $links[] = array( 'title' => '', 'url' => '' );

    echo '<div id="custom-links-container">';

    foreach ( $links as $index => $link ) {
        $title = isset( $link['title'] ) ? $link['title'] : '';
        $url = isset( $link['url'] ) ? $link['url'] : '';
        ?>
        <div class="custom-link-row" data-index="<?php echo esc_attr( $index ); ?>">
            <input type="text"
                   name="my_plugin_custom_links[<?php echo esc_attr( $index ); ?>][title]"
                   value="<?php echo esc_attr( $title ); ?>"
                   placeholder="链接标题"
                   class="regular-text" />
            <input type="url"
                   name="my_plugin_custom_links[<?php echo esc_attr( $index ); ?>][url]"
                   value="<?php echo esc_url( $url ); ?>"
                   placeholder="https://example.com"
                   class="regular-text" />
            <button type="button" class="button remove-link">移除</button>
        </div>
        <?php
    }

    echo '</div>';
    echo '<button type="button" id="add-link" class="button">添加链接</button>';

    // 简单的JavaScript用于动态添加/移除行
    ?>
    <script>
    jQuery(document).ready(function($) {
        $('#add-link').on('click', function() {
            var container = $('#custom-links-container');
            var index = container.find('.custom-link-row').length;
            var html = '<div class="custom-link-row" data-index="' + index + '">' +
                '<input type="text" name="my_plugin_custom_links[' + index + '][title]" value="" placeholder="链接标题" class="regular-text" /> ' +
                '<input type="url" name="my_plugin_custom_links[' + index + '][url]" value="" placeholder="https://example.com" class="regular-text" /> ' +
                '<button type="button" class="button remove-link">移除</button>' +
                '</div>';
            container.append(html);
        });

        $(document).on('click', '.remove-link', function() {
            if ($('.custom-link-row').length > 1) {
                $(this).closest('.custom-link-row').remove();
            }
        });
    });
    </script>
    <?php
}

// 对应的清理函数
function my_plugin_sanitize_custom_links( $input ) {
    if ( ! is_array( $input ) ) {
        return array();
    }

    $output = array();

    foreach ( $input as $link ) {
        if ( empty( $link['title'] ) && empty( $link['url'] ) ) {
            continue; // 跳过空行
        }

        $title = sanitize_text_field( $link['title'] );
        $url = esc_url_raw( $link['url'] );

        if ( ! empty( $title ) || ! empty( $url ) ) {
            $output[] = array(
                'title' => $title,
                'url'   => $url,
            );
        }
    }

    return $output;
}

易错点

  • 字段ID不唯一:在同一页面内,字段ID必须是唯一的,否则会导致HTML id属性冲突,可能影响JavaScript操作和CSS样式。
  • 页面slug不匹配add_settings_field$page 参数必须与 add_options_page$menu_slug 以及 add_settings_section$page 参数完全一致,包括大小写。
  • 章节ID不匹配:如果指定了 $section 参数,必须与 add_settings_section$id 完全一致,否则字段不会出现在预期的章节中。
  • 回调函数未输出正确的name属性:字段的 name 属性必须与 register_setting 的选项名匹配。对于简单选项,直接使用选项名;对于数组选项,使用 option_name[key] 格式。
  • 忘记转义输出:在回调函数中输出任何动态数据时,必须使用适当的转义函数。标签文本使用 esc_html,输入框值使用 esc_attr,URL使用 esc_url
  • 在回调函数中直接访问 $_POST:字段回调函数只负责输出HTML,不应该处理表单提交。表单数据处理应由 register_settingsanitize_callback 处理。
  • 使用 selected()checked() 函数不正确:这些辅助函数需要三个参数(当前值、比较值、是否回显),但第三个参数通常省略(默认为true)。确保比较逻辑正确。

最佳实践

使用 $args 参数传递上下文信息

通过 $args 参数向回调函数传递额外的上下文信息,可以使回调函数更加灵活和可复用。

// 定义字段时传递自定义参数
add_settings_field(
    'my_plugin_email_field',
    '邮箱地址',
    'my_plugin_generic_text_field_callback',
    'my-plugin-settings',
    'my_plugin_main_section',
    array(
        'label_for'   => 'my_plugin_email_field',
        'option_name' => 'my_plugin_settings',
        'field_key'   => 'email',
        'type'        => 'email', // 指定输入类型
        'description' => '请输入有效的邮箱地址',
        'class'       => 'regular-text ltr', // 添加CSS类
        'placeholder' => 'user@example.com',
    )
);

// 通用的文本字段回调函数
function my_plugin_generic_text_field_callback( $args ) {
    $options = get_option( $args['option_name'] );
    $value = isset( $options[ $args['field_key'] ] ) ? $options[ $args['field_key'] ] : '';

    $type = isset( $args['type'] ) ? $args['type'] : 'text';
    $class = isset( $args['class'] ) ? $args['class'] : 'regular-text';
    $placeholder = isset( $args['placeholder'] ) ? $args['placeholder'] : '';
    ?>
    <input type="<?php echo esc_attr( $type ); ?>"
           id="<?php echo esc_attr( $args['label_for'] ); ?>"
           name="<?php echo esc_attr( $args['option_name'] . '[' . $args['field_key'] . ']' ); ?>"
           value="<?php echo esc_attr( $value ); ?>"
           class="<?php echo esc_attr( $class ); ?>"
           placeholder="<?php echo esc_attr( $placeholder ); ?>"
           <?php echo ( isset( $args['required'] ) && $args['required'] ) ? 'required' : ''; ?> />

    <?php if ( ! empty( $args['description'] ) ) : ?>
        <p class="description"><?php echo esc_html( $args['description'] ); ?></p>
    <?php endif; ?>
    <?php
}

字段回调函数的模块化设计

将不同类型的字段抽象为独立的函数或类方法,提高代码复用性。

class My_Plugin_Field_Renderer {
    public static function render_text_field( $args ) {
        $defaults = array(
            'option_name' => '',
            'field_key'   => '',
            'value'       => '',
            'type'        => 'text',
            'class'       => 'regular-text',
            'placeholder' => '',
            'description' => '',
        );
        $args = wp_parse_args( $args, $defaults );
        ?>
        <input type="<?php echo esc_attr( $args['type'] ); ?>"
               id="<?php echo esc_attr( $args['field_key'] ); ?>"
               name="<?php echo esc_attr( $args['option_name'] . '[' . $args['field_key'] . ']' ); ?>"
               value="<?php echo esc_attr( $args['value'] ); ?>"
               class="<?php echo esc_attr( $args['class'] ); ?>"
               placeholder="<?php echo esc_attr( $args['placeholder'] ); ?>" />
        <?php if ( $args['description'] ) : ?>
            <p class="description"><?php echo esc_html( $args['description'] ); ?></p>
        <?php endif; ?>
        <?php
    }

    public static function render_select_field( $args ) {
        $defaults = array(
            'option_name' => '',
            'field_key'   => '',
            'value'       => '',
            'options'     => array(),
            'class'       => '',
            'description' => '',
        );
        $args = wp_parse_args( $args, $defaults );
        ?>
        <select id="<?php echo esc_attr( $args['field_key'] ); ?>"
                name="<?php echo esc_attr( $args['option_name'] . '[' . $args['field_key'] . ']' ); ?>"
                class="<?php echo esc_attr( $args['class'] ); ?>">
            <?php foreach ( $args['options'] as $option_value => $option_label ) : ?>
                <option value="<?php echo esc_attr( $option_value ); ?>"
                    <?php selected( $args['value'], $option_value ); ?>>
                    <?php echo esc_html( $option_label ); ?>
                </option>
            <?php endforeach; ?>
        </select>
        <?php if ( $args['description'] ) : ?>
            <p class="description"><?php echo esc_html( $args['description'] ); ?></p>
        <?php endif; ?>
        <?php
    }
}

// 在字段回调中使用
function my_plugin_special_field_callback() {
    $options = get_option( 'my_plugin_settings' );

    My_Plugin_Field_Renderer::render_text_field( array(
        'option_name' => 'my_plugin_settings',
        'field_key'   => 'special_field',
        'value'       => isset( $options['special_field'] ) ? $options['special_field'] : '',
        'type'        => 'text',
        'class'       => 'regular-text',
        'placeholder' => '请输入内容',
        'description' => '这是一个特殊字段',
    ) );
}

与 WordPress 核心UI样式保持一致

使用WordPress核心的CSS类来确保字段样式与后台其他部分一致,提供一致的用户体验。

  • regular-text:普通宽度文本输入框
  • large-text:宽文本输入框
  • small-text:小文本输入框
  • code:等宽字体,适合代码
  • color-picker:颜色选择器字段
  • date-picker:日期选择器字段
// 使用WordPress核心样式类
function my_plugin_field_with_classes_callback() {
    $value = get_option( 'my_plugin_field', '' );
    ?>
    <input type="text" class="regular-text code" value="<?php echo esc_attr( $value ); ?>" />
    <p class="description">使用 <code>regular-text code</code> 类显示等宽字体</p>

    <textarea class="large-text" rows="5"></textarea>
    <p class="description">使用 <code>large-text</code> 类创建多行文本框</p>

    <input type="text" class="small-text" value="100" />
    <p class="description">使用 <code>small-text</code> 类创建小宽度输入框</p>
    <?php
}

条件显示字段

根据其他字段的值动态显示或隐藏相关字段,提供更智能的用户界面。

// 在字段回调中添加JavaScript实现条件逻辑
function my_plugin_conditional_field_callback() {
    $options = get_option( 'my_plugin_settings' );
    $enable_feature = isset( $options['enable_feature'] ) ? $options['enable_feature'] : 'no';
    $feature_option = isset( $options['feature_option'] ) ? $options['feature_option'] : '';
    ?>
    <fieldset>
        <label>
            <input type="radio" name="my_plugin_settings[enable_feature]" value="yes" 
                <?php checked( $enable_feature, 'yes' ); ?> />
            启用功能
        </label><br>
        <label>
            <input type="radio" name="my_plugin_settings[enable_feature]" value="no" 
                <?php checked( $enable_feature, 'no' ); ?> />
            禁用功能
        </label>
    </fieldset>

    <div id="feature-options-wrapper" style="<?php echo ( 'yes' !== $enable_feature ) ? 'display:none;' : ''; ?>">
        <input type="text" 
               name="my_plugin_settings[feature_option]"
               value="<?php echo esc_attr( $feature_option ); ?>"
               class="regular-text"
               placeholder="功能选项" />
    </div>

    <script>
    jQuery(document).ready(function($) {
        $('input[name="my_plugin_settings[enable_feature]"]').on('change', function() {
            if ($(this).val() === 'yes') {
                $('#feature-options-wrapper').show();
            } else {
                $('#feature-options-wrapper').hide();
            }
        });
    });
    </script>
    <?php
}