WordPress函数:esc_html:转义HTML内容

编辑文章

简介

esc_html 是WordPress的核心安全函数,用于将文本中的特殊字符转换为HTML实体,确保文本在HTML页面中安全显示,防止跨站脚本攻击(XSS)。

语法

esc_html( string $text ): string

该函数位于 wp-includes/formatting.php 文件中,内部调用 htmlspecialchars() 函数,使用 ENT_QUOTESENT_SUBSTITUTE 标志,确保单引号和双引号都被转义,并用合适的Unicode替换字符处理无效的UTF-8序列。

参数 类型 默认值 说明
$text string 必需 需要转义的文本

返回值:转义后的HTML安全字符串。

用法

基础用法

最常见的使用场景是在输出动态内容到HTML标签之间时进行转义:

// 假设 $user_input 来自用户提交的数据
$user_input = $_POST['custom_title'];
?>

<h1><?php echo esc_html( $user_input ); ?></h1>
<p class="description">
    <?php echo esc_html( get_post_meta( get_the_ID(), 'subtitle', true ) ); ?>
</p>

进阶用法

在HTML属性中使用转义

虽然 esc_html 主要用于标签间的内容,但有时也会在属性值中使用,不过更推荐使用专用的 esc_attr() 函数:

// 不推荐 - 使用 esc_html 转义属性值
<div title="<?php echo esc_html( $dynamic_title ); ?>">内容</div>

// 推荐 - 使用 esc_attr 转义属性值
<div title="<?php echo esc_attr( $dynamic_title ); ?>">内容</div>

结合翻译函数使用

在需要国际化的主题或插件中,esc_html 常与翻译函数结合使用:

// 错误的做法 - 先转义后翻译会破坏翻译字符串
echo esc_html( __( 'Hello, World!', 'my-text-domain' ) );

// 正确的做法 - 先翻译后转义
echo esc_html__( 'Hello, World!', 'my-text-domain' );

// 或者使用等效的组合方式
echo esc_html( __( 'Hello, World!', 'my-text-domain' ) );

易错点

在需要保留HTML的地方误用

esc_html 会转义所有HTML标签,如果在需要保留部分HTML格式的地方使用,会导致标签被显示为纯文本:

// 错误:富文本内容被完全转义
$rich_content = '<p>这是<strong>重要</strong>内容</p>';
echo esc_html( $rich_content ); // 输出:<p>这是<strong>重要</strong>内容</p>

// 正确:使用 wp_kses_post 允许安全的HTML标签
echo wp_kses_post( $rich_content ); // 正确渲染HTML

重复转义导致双重编码

多次对同一内容使用转义函数会导致双重编码,显示异常:

$text = 'AT&T & "Quoted"';

// 错误的重复转义
echo esc_html( esc_html( $text ) ); 
// 输出:AT&T & "Quoted"

// 正确:只需转义一次
echo esc_html( $text );
// 输出:AT&T & "Quoted"

对已经转义的数据再次转义

从某些WordPress函数获取的数据可能已经转义,再次转义会导致问题:

// 错误:the_title() 默认已转义
echo esc_html( the_title( '', '', false ) );

// 正确:直接输出或使用 get_the_title() 需要转义
echo esc_html( get_the_title() );

在JavaScript变量中错误使用

esc_html 不适用于JavaScript代码中的字符串,需要使用专门的函数:

// 危险:esc_html 不能防止JavaScript注入
<script>
var userData = '<?php echo esc_html( $user_input ); ?>'; // 仍然可能被注入
</script>

// 正确:使用 wp_json_encode 或 esc_js
<script>
var userData = <?php echo wp_json_encode( $user_input ); ?>;
</script>

最佳实践

输出前最后一步转义

遵循”尽可能晚地转义”原则,在数据即将输出到浏览器时进行转义,而不是在数据处理阶段:

// 不好的做法:在存储或处理时转义
function process_data( $input ) {
    $cleaned = esc_html( $input ); // 过早转义
    // ... 其他处理
    return $cleaned; // 返回的是转义后的HTML实体
}

// 好的做法:在输出时转义
function process_data( $input ) {
    // ... 各种处理
    return $input; // 返回原始数据
}

// 在模板中输出时转义
echo esc_html( process_data( $user_input ) );

根据上下文选择转义函数

WordPress提供了一系列转义函数,根据输出位置选择合适的方法:

// 在HTML标签内容中使用
echo esc_html( $text );

// 在HTML属性中使用
echo '<input value="' . esc_attr( $value ) . '">';

// 在HTML标签的href、src等URL属性中使用
echo '<a href="' . esc_url( $url ) . '">链接</a>';

// 在JavaScript中使用
echo '<script>var data = ' . wp_json_encode( $js_data ) . ';</script>';

// 在SQL查询中使用(已弃用,推荐使用$wpdb->prepare)
// 使用 $wpdb->prepare() 替代

对翻译字符串的安全处理

WordPress提供了安全的翻译函数变体,简化了转义流程:

// 标准的翻译+转义组合
esc_html_e( 'Hello World', 'text-domain' ); // 输出转义后的翻译
esc_attr_e( 'Title Text', 'text-domain' );   // 属性值的翻译+转义

// 返回转义后的翻译字符串
$translated = esc_html__( 'Hello World', 'text-domain' );
$translated_attr = esc_attr__( 'Title Text', 'text-domain' );

与现代开发模式结合

在REST API响应中

虽然REST API通常返回JSON,但在处理HTML内容时仍需注意:

register_rest_route( 'my-plugin/v1', '/content', array(
    'methods'  => 'GET',
    'callback' => function( $request ) {
        $content = get_post_field( 'post_content', $request['id'] );

        // API响应中可能需要返回原始和转义两种格式
        return array(
            'raw'   => $content, // 原始内容,用于编辑
            'safe'  => wp_kses_post( $content ), // 安全的内容,用于显示
        );
    },
) );

在Gutenberg区块开发中

区块的save函数需要特别注意输出安全:

registerBlockType( 'my-plugin/my-block', {
    // ... 其他配置

    save: function( props ) {
        // 在save函数中,使用dangerouslySetInnerHTML需要确保内容安全
        return el( 'div', {
            dangerouslySetInnerHTML: {
                // WordPress会自动转义,但双重检查更安全
                __html: wp.element.RawHTML.protect( props.attributes.content )
            }
        } );
    }
} );

性能考虑

虽然转义函数的性能开销很小,但在循环中大量使用时仍需注意:

// 在循环中避免重复调用相同的转义
$escaped_titles = array();

// 预先转义可以减少重复处理
foreach ( $posts as $post ) {
    $escaped_titles[] = esc_html( get_the_title( $post ) );
}

// 然后在模板中使用已转义的数据
foreach ( $escaped_titles as $title ) {
    echo '<li>' . $title . '</li>'; // 注意:这里不需要再次转义
}

记住:esc_html 不是数据验证或清理函数,它只负责输出安全。在存储到数据库前,仍需使用适当的清理函数(如 sanitize_text_field)处理用户输入。输出转义和输入清理是两个独立但互补的安全层。