WordPress函数:do_settings_fields 输出设置字段组

编辑文章

简介

do_settings_sections 函数用于在WordPress后台设置页面中输出已注册的设置节(sections)及其包含的所有设置字段,是构建自定义设置页面的核心工具。

语法

do_settings_sections( string $page )

文件位置wp-admin/includes/template.php
内部调用:函数会调用 do_settings_fields() 来输出每个节内的具体字段。

参数 类型 必填 默认值 说明
$page string 设置页面的菜单slug,需要与 add_settings_section()add_settings_field() 注册时使用的页面slug一致

返回值:无返回值,直接输出HTML内容。

用法

基础用法

最基本的用法是在设置页面回调函数中调用 do_settings_sections(),通常与 settings_fields() 函数配合使用:

// 在主题的 functions.php 或插件主文件中添加
add_action('admin_menu', 'myplugin_register_settings_page');

function myplugin_register_settings_page() {
    add_options_page(
        '我的插件设置',
        '我的插件',
        'manage_options',
        'myplugin-settings',
        'myplugin_render_settings_page'
    );

    // 注册设置、节和字段
    register_setting('myplugin_options', 'myplugin_settings');

    add_settings_section(
        'myplugin_main_section',
        '主要设置',
        'myplugin_section_callback',
        'myplugin-settings'
    );

    add_settings_field(
        'api_key',
        'API密钥',
        'myplugin_field_callback',
        'myplugin-settings',
        'myplugin_main_section',
        ['label_for' => 'api_key']
    );
}

// 设置页面渲染函数
function myplugin_render_settings_page() {
    ?>
    <div class="wrap">
        <h1>我的插件设置</h1>
        <form action="options.php" method="post">
            <?php
            // 输出必要的安全字段
            settings_fields('myplugin_options');
            // 输出所有设置节和字段
            do_settings_sections('myplugin-settings');
            // 输出提交按钮
            submit_button();
            ?>
        </form>
    </div>
    <?php
}

// 节描述回调函数
function myplugin_section_callback($args) {
    echo '<p>这里是主要设置的说明文字。</p>';
}

// 字段渲染回调函数
function myplugin_field_callback($args) {
    $options = get_option('myplugin_settings');
    $value = isset($options[$args['label_for']]) ? $options[$args['label_for']] : '';
    ?>
    <input type="text" 
           id="<?php echo esc_attr($args['label_for']); ?>" 
           name="myplugin_settings[<?php echo esc_attr($args['label_for']); ?>]" 
           value="<?php echo esc_attr($value); ?>" 
           class="regular-text" />
    <?php
}

为什么需要完整的流程do_settings_sections() 只是输出环节,它依赖于之前正确的注册流程。如果忘记调用 settings_fields(),WordPress的安全验证会失败;如果忘记注册设置节,函数将无内容可输出。

进阶用法

自定义节的输出顺序

虽然 do_settings_sections() 按注册顺序输出,但可以通过调整注册顺序或使用优先级来控制:

// 在插件初始化时按特定顺序注册节
add_action('admin_init', 'myplugin_register_sections_in_order', 5);

function myplugin_register_sections_in_order() {
    // 先注册的节会先显示
    add_settings_section('section_one', '第一部分', '__return_empty_string', 'myplugin-settings');
    add_settings_section('section_two', '第二部分', '__return_empty_string', 'myplugin-settings');

    // 为每个节添加字段
    add_settings_field('field1', '字段1', 'render_field1', 'myplugin-settings', 'section_one');
    add_settings_field('field2', '字段2', 'render_field2', 'myplugin-settings', 'section_two');
}

条件性输出节

在某些情况下,你可能需要根据条件显示或隐藏整个设置节:

function myplugin_conditional_settings_page() {
    settings_fields('myplugin_options');

    // 只输出特定节
    global $wp_settings_sections;
    $page = 'myplugin-settings';

    if (isset($wp_settings_sections[$page])) {
        foreach ((array) $wp_settings_sections[$page] as $section) {
            // 检查用户权限或其他条件
            if (current_user_can('manage_options') || $section['id'] === 'public_section') {
                echo '<div class="settings-section" id="' . esc_attr($section['id']) . '">';
                if ($section['title']) {
                    echo "<h2>{$section['title']}</h2>\n";
                }
                if ($section['callback']) {
                    call_user_func($section['callback'], $section);
                }
                echo '<table class="form-table" role="presentation">';
                do_settings_fields($page, $section['id']);
                echo '</table></div>';
            }
        }
    }

    submit_button();
}

易错点

页面slug不匹配

最常见的错误是 do_settings_sections() 的参数与注册时使用的页面slug不一致:

// 错误示例
add_settings_section('my_section', '我的节', 'callback', 'myplugin-page');
// 在渲染时使用了不同的slug
do_settings_sections('myplugin-page-2'); // 这里不会输出任何内容

// 正确做法:保持slug一致
do_settings_sections('myplugin-page'); // 与注册时使用的slug相同

忘记调用 settings_fields()

如果忘记在表单中调用 settings_fields(),WordPress的安全nonce验证会失败,导致设置无法保存:

// 错误:缺少安全字段
<form method="post" action="options.php">
    <?php do_settings_sections('myplugin-settings'); ?>
    <?php submit_button(); ?>
</form>

// 正确:包含安全字段
<form method="post" action="options.php">
    <?php settings_fields('myplugin_options'); ?>
    <?php do_settings_sections('myplugin-settings'); ?>
    <?php submit_button(); ?>
</form>

在错误的钩子中注册

设置节和字段必须在 admin_init 钩子或之前注册,否则 do_settings_sections() 调用时它们还不存在:

// 错误:在太晚的钩子中注册
add_action('admin_menu', function() {
    // 当 do_settings_sections() 在页面回调中执行时,这里的注册还未发生
    add_settings_section('my_section', '我的节', 'callback', 'myplugin-settings');
});

// 正确:在 admin_init 钩子中注册
add_action('admin_init', function() {
    add_settings_section('my_section', '我的节', 'callback', 'myplugin-settings');
});

输出转义不足

在字段回调函数中直接输出用户数据而不转义会导致XSS安全漏洞:

// 错误:未转义输出
function myplugin_field_callback($args) {
    $options = get_option('myplugin_settings');
    echo '<input value="' . $options['api_key'] . '">'; // 未转义!
}

// 正确:始终转义输出
function myplugin_field_callback($args) {
    $options = get_option('myplugin_settings');
    $value = isset($options['api_key']) ? esc_attr($options['api_key']) : '';
    echo '<input value="' . $value . '">';
}

最佳实践

模块化组织设置代码

对于包含多个设置节的复杂插件,将设置代码模块化可以提高可维护性:

// 主设置类
class MyPlugin_Settings {
    private $page_slug = 'myplugin-settings';

    public function __construct() {
        add_action('admin_init', [$this, 'register_settings']);
        add_action('admin_menu', [$this, 'add_menu_page']);
    }

    public function register_settings() {
        register_setting('myplugin_options', 'myplugin_settings', [
            'sanitize_callback' => [$this, 'sanitize_settings']
        ]);

        // 使用单独的方法注册每个节
        $this->register_general_section();
        $this->register_advanced_section();
    }

    private function register_general_section() {
        add_settings_section(
            'general',
            '常规设置',
            function() {
                echo '<p>常规设置说明</p>';
            },
            $this->page_slug
        );

        add_settings_field(
            'site_title',
            '网站标题',
            [$this, 'render_text_field'],
            $this->page_slug,
            'general',
            [
                'label_for' => 'site_title',
                'description' => '输入网站标题'
            ]
        );
    }

    public function render_text_field($args) {
        $options = get_option('myplugin_settings');
        $value = isset($options[$args['label_for']]) ? $options[$args['label_for']] : '';
        ?>
        <input type="text" 
               id="<?php echo esc_attr($args['label_for']); ?>"
               name="myplugin_settings[<?php echo esc_attr($args['label_for']); ?>]"
               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;
    }
}

与现代JavaScript框架结合

当使用React或Vue开发WordPress插件时,可以结合REST API和设置API:

// 注册REST API端点用于设置
add_action('rest_api_init', function() {
    register_rest_route('myplugin/v1', '/settings', [
        'methods' => 'GET',
        'callback' => 'myplugin_get_settings_api',
        'permission_callback' => function() {
            return current_user_can('manage_options');
        }
    ]);

    register_rest_route('myplugin/v1', '/settings', [
        'methods' => 'POST',
        'callback' => 'myplugin_update_settings_api',
        'permission_callback' => function() {
            return current_user_can('manage_options');
        }
    ]);
});

// 同时保留传统的设置页面作为备选
function myplugin_hybrid_settings_page() {
    ?>
    <div class="wrap">
        <h1>混合设置页面</h1>

        <!-- 传统表单部分 -->
        <div id="traditional-settings">
            <form method="post" action="options.php">
                <?php settings_fields('myplugin_options'); ?>
                <?php do_settings_sections('myplugin-settings'); ?>
                <?php submit_button(); ?>
            </form>
        </div>

        <!-- React应用容器 -->
        <div id="react-settings-app"></div>

        <script>
        // 根据用户选择切换界面
        if (window.myPluginUseReact) {
            document.getElementById('traditional-settings').style.display = 'none';
            // 初始化React应用
        }
        </script>
    </div>
    <?php
}

性能优化

对于有大量设置的页面,可以考虑分页或选项卡式显示:

function myplugin_tabbed_settings_page() {
    $tabs = ['general', 'advanced', 'tools'];
    $current_tab = isset($_GET['tab']) && in_array($_GET['tab'], $tabs) ? $_GET['tab'] : 'general';
    ?>
    <div class="wrap">
        <h1>我的插件设置</h1>

        <nav class="nav-tab-wrapper">
            <?php foreach ($tabs as $tab): ?>
                <a href="?page=myplugin-settings&tab=<?php echo esc_attr($tab); ?>"
                   class="nav-tab <?php echo $current_tab === $tab ? 'nav-tab-active' : ''; ?>">
                    <?php echo esc_html(ucfirst($tab)); ?>
                </a>
            <?php endforeach; ?>
        </nav>

        <form method="post" action="options.php">
            <?php settings_fields('myplugin_options'); ?>

            <!-- 只输出当前选项卡的节 -->
            <?php 
            // 根据选项卡显示不同的节
            switch ($current_tab) {
                case 'general':
                    do_settings_sections('myplugin-settings-general');
                    break;
                case 'advanced':
                    do_settings_sections('myplugin-settings-advanced');
                    break;
            }
            ?>

            <?php submit_button(); ?>
        </form>
    </div>
    <?php
}

可访问性增强

确保设置页面符合WCAG标准:

function myplugin_accessible_field_callback($args) {
    $options = get_option('myplugin_settings');
    $value = isset($options[$args['label_for']]) ? $options[$args['label_for']] : '';
    $describedby = !empty($args['description']) ? ' aria-describedby="' . esc_attr($args['label_for']) . '-description"' : '';
    ?>
    <input type="text" 
           id="<?php echo esc_attr($args['label_for']); ?>"
           name="myplugin_settings[<?php echo esc_attr($args['label_for']); ?>]"
           value="<?php echo esc_attr($value); ?>"
           class="regular-text"
           <?php echo $describedby; ?>
           aria-required="<?php echo !empty($args['required']) ? 'true' : 'false'; ?>" />

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

通过遵循这些最佳实践,你可以创建出既安全又易用、既高效又可维护的设置页面,同时为未来的功能扩展和现代开发模式做好准备。