WordPress函数:add_settings_error:在WordPress设置页面显示用户反馈信息

编辑文章

简介

add_settings_error 是WordPress设置API的核心组件之一,专门用于在后台管理界面向用户提供操作反馈。它允许开发者在验证设置数据或执行关键操作后,向管理员显示成功、错误、警告或信息提示,从而构建更加友好、直观的管理界面。

语法

add_settings_error( $setting, $code, $message, $type = 'error' )

此函数定义于 wp-admin/includes/template.php 文件中。它并不直接输出HTML,而是将消息存储到全局的 $wp_settings_errors 数组中,等待稍后由 settings_errors() 函数统一渲染显示。

参数名 类型 默认值 详细说明
$setting 字符串 设置标识符。通常对应 register_setting() 中注册的选项名。这个参数将消息与特定的设置组关联起来,确保消息显示在正确的上下文中。
$code 版本提示 唯一标识码。WordPress 2.7.0引入此函数,所有参数从最初版本就存在且行为一致。
$message 字符串 展示给用户的文本内容。应当清晰、友好,如果可能还应提供解决问题的指导。
$type 字符串 'error' 消息类型,决定前端显示的视觉样式。可选值:'error'(红色,表示严重问题)、'warning'(黄色,表示需要注意)、'info'(蓝色,表示一般信息)、'success'(绿色,表示操作成功)。

返回值:无返回值。

用法

基础用法

最常见的应用场景是在主题或插件设置页面的数据净化回调函数中使用。当用户提交表单时,WordPress会自动调用这个回调函数,开发者可以在其中验证数据并添加相应的反馈信息。

// 在admin_init钩子中注册设置
add_action( 'admin_init', 'my_plugin_register_settings' );

function my_plugin_register_settings() {
    // 注册设置组和净化回调函数
    register_setting(
        'my_plugin_options',      // 选项组名称
        'my_plugin_settings',     // 存储在wp_options表中的选项名
        'my_plugin_sanitize_options' // 净化回调函数
    );

    // 添加设置字段
    add_settings_field(
        'api_key',
        'API密钥',
        'my_plugin_api_key_field_callback',
        'my-plugin-settings-page',
        'main_section'
    );
}

/**
 * 净化回调函数 - 验证用户输入并添加反馈信息
 * 
 * @param array $input 用户提交的原始数据
 * @return array 净化后准备存入数据库的数据
 */
function my_plugin_sanitize_options( $input ) {
    $output = get_option( 'my_plugin_settings', array() );
    $has_error = false;

    // 验证API密钥(必须为32位十六进制字符串)
    if ( isset( $input['api_key'] ) ) {
        $api_key = sanitize_text_field( trim( $input['api_key'] ) );

        if ( empty( $api_key ) ) {
            add_settings_error(
                'my_plugin_settings',           // 与register_setting的选项名一致
                'api_key_empty',
                'API密钥不能为空,请填写有效的API密钥。',
                'error'
            );
            $has_error = true;
        } elseif ( ! preg_match( '/^[a-f0-9]{32}$/i', $api_key ) ) {
            add_settings_error(
                'my_plugin_settings',
                'api_key_invalid',
                'API密钥格式不正确。请输入32位十六进制字符串。',
                'error'
            );
            $has_error = true;
        } else {
            $output['api_key'] = $api_key;
        }
    }

    // 验证缓存时间(必须在1-3600秒之间)
    if ( isset( $input['cache_time'] ) ) {
        $cache_time = absint( $input['cache_time'] );

        if ( $cache_time < 1 || $cache_time > 3600 ) {
            add_settings_error(
                'my_plugin_settings',
                'cache_time_invalid',
                '缓存时间必须在1到3600秒之间。',
                'warning'  // 使用warning而不是error,因为系统会使用默认值
            );
            // 使用默认值
            $output['cache_time'] = 300;
        } else {
            $output['cache_time'] = $cache_time;
        }
    }

    // 所有验证通过,显示成功消息
    if ( ! empty( $input ) && ! $has_error ) {
        add_settings_error(
            'my_plugin_settings',
            'settings_updated',
            '设置已成功保存!',
            'success'
        );
    }

    return $output;
}

/**
 * 在设置页面输出错误信息
 * 这个函数应该在设置页面的适当位置调用
 */
function my_plugin_display_settings_page() {
    ?>
    <div class="wrap">
        <h1>我的插件设置</h1>

        <?php 
        // 关键:输出所有排队中的设置错误/成功信息
        settings_errors( 'my_plugin_settings' );
        ?>

        <form method="post" action="options.php">
            <?php
            settings_fields( 'my_plugin_options' );
            do_settings_sections( 'my-plugin-settings-page' );
            submit_button();
            ?>
        </form>
    </div>
    <?php
}

进阶用法

虽然 add_settings_error 主要设计用于设置API,但其底层机制允许开发者在任何后台管理页面使用它,前提是正确处理消息的持久化和显示。

/**
 * 在自定义管理页面处理批量操作并显示反馈
 * 此示例展示如何处理数据导入操作
 */

// 注册自定义管理页面
add_action( 'admin_menu', 'my_plugin_add_import_page' );

function my_plugin_add_import_page() {
    add_submenu_page(
        'tools.php',
        '数据导入',
        '数据导入',
        'manage_options',
        'my-plugin-import',
        'my_plugin_render_import_page'
    );
}

/**
 * 处理导入表单提交(使用admin-post.php模式)
 */
add_action( 'admin_post_my_plugin_import_data', 'my_plugin_handle_import' );

function my_plugin_handle_import() {
    // 安全检查:权限和非ce验证
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_die( '权限不足。' );
    }

    check_admin_referer( 'my_plugin_import_action' );

    // 处理文件上传
    if ( ! isset( $_FILES['import_file'] ) || $_FILES['import_file']['error'] !== UPLOAD_ERR_OK ) {
        add_settings_error(
            'my_plugin_import',
            'no_file',
            '请选择要导入的文件。',
            'error'
        );
    } else {
        // 验证文件类型(安全关键步骤!)
        $file_info = wp_check_filetype_and_ext( 
            $_FILES['import_file']['tmp_name'], 
            $_FILES['import_file']['name'] 
        );

        if ( ! in_array( $file_info['type'], array( 'text/csv', 'application/json' ) ) ) {
            add_settings_error(
                'my_plugin_import',
                'invalid_file_type',
                '只支持CSV或JSON格式的文件。',
                'error'
            );
        } else {
            // 处理导入逻辑
            $result = my_plugin_process_import_file( $_FILES['import_file']['tmp_name'] );

            if ( is_wp_error( $result ) ) {
                add_settings_error(
                    'my_plugin_import',
                    'import_failed',
                    sprintf( '导入失败:%s', esc_html( $result->get_error_message() ) ),
                    'error'
                );
            } else {
                add_settings_error(
                    'my_plugin_import',
                    'import_success',
                    sprintf( 
                        '成功导入 %d 条记录。%s',
                        $result['count'],
                        ! empty( $result['skipped'] ) ? sprintf( '跳过了 %d 条无效记录。', $result['skipped'] ) : ''
                    ),
                    'success'
                );
            }
        }
    }

    // 保存消息到瞬态,以便在重定向后显示
    set_transient( 'my_plugin_import_errors', get_settings_errors( 'my_plugin_import' ), 45 );

    // 重定向回原页面,防止刷新重复提交
    wp_safe_redirect( 
        add_query_arg( 
            array( 'my_plugin_imported' => 'true' ),
            admin_url( 'tools.php?page=my-plugin-import' )
        ) 
    );
    exit;
}

/**
 * 渲染导入页面
 */
function my_plugin_render_import_page() {
    // 从瞬态中恢复消息(如果存在)
    if ( isset( $_GET['my_plugin_imported'] ) && $_GET['my_plugin_imported'] === 'true' ) {
        $errors = get_transient( 'my_plugin_import_errors' );
        if ( $errors ) {
            global $wp_settings_errors;
            $wp_settings_errors = is_array( $errors ) ? $errors : array();
            delete_transient( 'my_plugin_import_errors' );
        }
    }

    ?>
    <div class="wrap">
        <h1>数据导入</h1>

        <?php 
        // 显示所有与导入相关的消息
        settings_errors( 'my_plugin_import' );
        ?>

        <div class="card">
            <h2>导入数据</h2>
            <form method="post" enctype="multipart/form-data" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
                <?php wp_nonce_field( 'my_plugin_import_action' ); ?>
                <input type="hidden" name="action" value="my_plugin_import_data">

                <table class="form-table">
                    <tr>
                        <th scope="row">
                            <label for="import_file">选择文件</label>
                        </th>
                        <td>
                            <input type="file" name="import_file" id="import_file" accept=".csv,.json">
                            <p class="description">支持CSV或JSON格式,最大文件大小:<?php echo esc_html( size_format( wp_max_upload_size() ) ); ?></p>
                        </td>
                    </tr>
                </table>

                <?php submit_button( '开始导入' ); ?>
            </form>
        </div>
    </div>
    <?php
}

易错点

错误1:$setting 参数与实际选项名不匹配

问题:在 add_settings_error() 中使用的 $setting 参数与 register_setting()get_option() 中使用的选项名不一致。
原因:WordPress通过 $setting 参数来分组消息。当调用 settings_errors( $setting ) 时,只会显示与该标识符匹配的消息。如果不一致,即使添加了消息也不会显示。

错误2:忘记调用 settings_errors() 显示消息

问题:代码中添加了错误消息,但用户从未看到任何反馈。
原因add_settings_error() 仅将消息加入队列,必须在前端页面的适当位置调用 settings_errors() 函数来实际输出这些消息。新手开发者常常忘记在设置页面模板中添加这行关键代码。

错误3:在消息中直接输出未转义的用户数据

问题

// 危险!未转义的用户输入
add_settings_error( 'my_options', 'error', "用户 {$username} 提交了无效数据", 'error' );

风险:如果 $username 包含恶意JavaScript代码(如 <script>alert('xss')</script>),这段代码将在后台执行,可能导致跨站脚本攻击(XSS)。

错误4:在 sanitize_callback 中验证失败后仍保存数据

问题

function my_sanitize( $input ) {
    if ( ! validate_data( $input ) ) {
        add_settings_error( 'options', 'error', '数据无效', 'error' );
        // 错误:没有return,代码继续执行
    }
    return $input; // 无效数据被保存!
}

后果:无效数据被存入数据库,破坏了数据完整性,可能导致后续功能出错。

最佳实践

消息设计:清晰、友好、可操作

为什么重要:错误信息是用户界面的一部分,直接影响用户体验和产品专业性。
如何实施
使用完整句子:避免使用技术术语或缩写。
包含具体信息:指出哪个字段有问题,为什么有问题。
提供解决方案:告诉用户如何修复问题。
保持一致性:在整个插件中使用相似的语言风格。

// 差:模糊不清
add_settings_error( 'options', 'error', '无效输入', 'error' );

// 好:清晰明确
add_settings_error( 
    'options', 
    'email_invalid', 
    '在"通知邮箱"字段中,您输入的 "admin@example" 不是有效的电子邮件地址。请检查格式并重新输入。', 
    'error' 
);

安全性:始终转义动态内容

为什么重要:即使数据来自”可信”源,也要遵循”从不信任用户输入”的安全原则。
如何实施
对所有动态数据使用转义函数esc_html(), esc_attr(), esc_url() 等。
使用 sprintf() 安全拼接字符串:清晰区分静态文本和动态部分。

// 安全的消息构建方式
$user_value = $_POST['some_field']; // 来自用户输入
$max_length = 100; // 系统限制

add_settings_error(
    'my_settings',
    'value_too_long',
    sprintf(
        '在"%s"字段中,您输入了 %d 个字符,超过了最大限制 %d 个字符。',
        esc_html( '描述字段' ),      // 字段名
        absint( strlen( $user_value ) ), // 用户输入长度
        absint( $max_length )            // 系统限制
    ),
    'error'
);

代码组织:创建消息管理系统

为什么重要:随着插件规模增长,散布在各处的消息文本难以维护和国际化。
如何实施

class My_Plugin_Messages {
    /**
     * 获取消息文本
     * 
     * @param string $code 消息代码
     * @param array  $placeholders 占位符替换值
     * @return string 本地化的消息文本
     */
    public static function get( $code, $placeholders = array() ) {
        $messages = array(
            'success_save' => __( '设置已成功保存。', 'my-plugin' ),
            'error_required' => __( '%s字段是必填项,请填写后再保存。', 'my-plugin' ),
            'error_email' => __( '%s不是一个有效的电子邮件地址。', 'my-plugin' ),
            'warning_deprecated' => __( '%s功能将在下一版本中移除,请考虑使用%s替代。', 'my-plugin' ),
        );

        if ( ! isset( $messages[ $code ] ) ) {
            return '';
        }

        $message = $messages[ $code ];

        // 替换占位符
        if ( ! empty( $placeholders ) ) {
            $message = vsprintf( $message, array_map( 'esc_html', $placeholders ) );
        }

        return $message;
    }

    /**
     * 添加设置错误消息
     */
    public static function add_error( $setting, $code, $type = 'error', $placeholders = array() ) {
        $message = self::get( $code, $placeholders );
        if ( ! empty( $message ) ) {
            add_settings_error( $setting, $code, $message, $type );
        }
    }
}

// 使用示例
My_Plugin_Messages::add_error( 
    'my_settings', 
    'error_email',
    'error',
    array( 'user@invalid' )  // 替换%s占位符
);

与现代开发结合:REST API端点中的错误处理

为什么重要:随着WordPress向REST API和JavaScript驱动界面的演进,传统PHP消息模式需要调整。
如何实施

// REST API端点中的错误处理
register_rest_route( 'my-plugin/v1', '/settings', array(
    'methods'  => 'POST',
    'callback' => function( WP_REST_Request $request ) {
        $settings = $request->get_param( 'settings' );
        $errors   = array();

        // 验证数据
        if ( empty( $settings['api_key'] ) ) {
            $errors[] = array(
                'code'    => 'api_key_required',
                'message' => __( 'API密钥是必填项。', 'my-plugin' ),
                'field'   => 'api_key'
            );
        }

        // 如果有错误,返回错误响应
        if ( ! empty( $errors ) ) {
            return new WP_Error(
                'validation_failed',
                __( '设置验证失败。', 'my-plugin' ),
                array( 
                    'status' => 400,
                    'errors' => $errors 
                )
            );
        }

        // 保存设置
        update_option( 'my_plugin_settings', $settings );

        return rest_ensure_response( array(
            'success' => true,
            'message' => __( '设置已更新。', 'my-plugin' )
        ) );
    },
    'permission_callback' => function() {
        return current_user_can( 'manage_options' );
    }
) );

前端JavaScript处理

// React组件示例
const saveSettings = async (settings) => {
    try {
        const response = await wp.apiFetch({
            path: '/my-plugin/v1/settings',
            method: 'POST',
            data: { settings }
        });

        // 显示成功消息
        setNotice({
            type: 'success',
            message: response.message
        });
    } catch (error) {
        // 显示错误消息
        if (error.code === 'validation_failed') {
            error.errors.forEach(err => {
                setFieldError(err.field, err.message);
            });
        }
        setNotice({
            type: 'error',
            message: error.message
        });
    }
};

通过遵循这些最佳实践,您不仅可以正确使用 add_settings_error 函数,还能构建出安全、可维护且用户体验良好的WordPress插件设置界面。记住,良好的错误处理不仅是技术需求,更是优秀用户体验的重要组成部分。