WordPress函数:shortcode_atts 安全处理短代码属性

编辑文章

简介

shortcode_atts 是 WordPress 中用于处理短代码属性的核心函数,它能将用户输入的属性与默认值安全合并,并过滤未定义的属性,确保短代码安全稳定运行。

语法

shortcode_atts( array $defaults, array $provided, string $shortcode = '' )

位置: wp-includes/shortcodes.php

参数说明:

参数 类型 描述
$defaults 数组 短代码支持的属性及其默认值
$provided 数组 用户实际提供的属性(通常从 $atts 获取)
$shortcode 字符串 短代码名称(可选,用于过滤钩子)

返回值: 合并后的属性数组,已过滤无效属性并应用了类型转换。

用法

在短代码回调函数中,首先使用 shortcode_atts 处理属性:

function my_shortcode_callback( $atts, $content = null ) {
    // 定义默认值并合并用户输入
    $atts = shortcode_atts(
        array(
            'title' => '默认标题',
            'count' => 5,
            'type'  => 'post'
        ),
        $atts,
        'my_shortcode'
    );

    // 现在可以安全使用 $atts['title'], $atts['count'] 等
    return '<h2>' . esc_html( $atts['title'] ) . '</h2>';
}
add_shortcode( 'my_shortcode', 'my_shortcode_callback' );

易错点

直接使用未处理的 $atts

错误做法:

function bad_shortcode( $atts ) {
    // 直接访问可能不存在的数组键
    $title = $atts['title']; // 可能触发 PHP 警告
    return $title;
}

原因: 用户可能不提供 title 属性,导致未定义索引错误,且无法控制默认值。

忽略类型转换

错误做法:

$atts = shortcode_atts(
    array( 'count' => '5' ),
    $atts
);
$query_count = $atts['count'] * 2; // 字符串运算可能产生意外结果

原因: 短代码属性始终是字符串,数学运算前需要类型转换。

忘记转义输出

错误做法:

$atts = shortcode_atts( array( 'title' => '' ), $atts );
echo "<div>$atts[title]</div>"; // XSS 漏洞!

原因: 用户输入的属性可能包含恶意脚本,直接输出会导致跨站脚本攻击。

误解第三个参数

错误理解: 认为 $shortcode 参数只是用于调试的标识。

实际情况: 该参数会生成 shortcode_atts_{$shortcode} 过滤钩子,是扩展短代码属性的重要机制。

最佳实践

安全性强化

输入验证与清理

shortcode_atts 处理后进行额外的数据验证:

function secure_shortcode( $atts ) {
    $defaults = array(
        'user_id'   => 0,
        'status'    => 'publish',
        'meta_key'  => ''
    );

    $atts = shortcode_atts( $defaults, $atts, 'secure_example' );

    // 额外验证
    $atts['user_id'] = absint( $atts['user_id'] );

    // 限制可选值
    $allowed_statuses = array( 'publish', 'draft', 'private' );
    if ( ! in_array( $atts['status'], $allowed_statuses, true ) ) {
        $atts['status'] = $defaults['status'];
    }

    // 清理字符串
    $atts['meta_key'] = sanitize_key( $atts['meta_key'] );

    // 数据库查询时使用 prepare
    global $wpdb;
    $query = $wpdb->prepare(
        "SELECT * FROM $wpdb->posts WHERE post_author = %d AND post_status = %s",
        $atts['user_id'],
        $atts['status']
    );

    // ... 其余逻辑
}

输出转义

根据输出上下文使用正确的转义函数:

function output_shortcode( $atts ) {
    $atts = shortcode_atts( array(
        'title'       => '',
        'description' => '',
        'url'         => ''
    ), $atts, 'output_example' );

    ob_start();
    ?>
    <div class="shortcode-output">
        <!-- 普通文本转义 -->
        <h2><?php echo esc_html( $atts['title'] ); ?></h2>

        <!-- 允许少量 HTML -->
        <p><?php echo wp_kses_post( $atts['description'] ); ?></p>

        <!-- URL 属性转义 -->
        <a href="<?php echo esc_url( $atts['url'] ); ?>">链接</a>

        <!-- 在 HTML 属性中使用 -->
        <div data-config="<?php echo esc_attr( wp_json_encode( $atts ) ); ?>">
            内容
        </div>
    </div>
    <?php

    return ob_get_clean();
}

性能优化

合理设置默认值

避免在默认值中执行昂贵操作:

// 不推荐 - 每次短代码调用都会执行查询
$defaults = array(
    'category' => get_queried_object_id() // 每次都会执行
);

// 推荐 - 使用回调或条件逻辑
function optimized_shortcode( $atts ) {
    // 延迟获取动态默认值
    $category_id = isset( $atts['category'] ) ? $atts['category'] : get_queried_object_id();

    $atts = shortcode_atts(
        array(
            'category' => 0, // 使用占位值
            'count'    => 5
        ),
        $atts,
        'optimized'
    );

    // 动态替换占位值
    if ( 0 === $atts['category'] ) {
        $atts['category'] = $category_id;
    }
}

使用过滤钩子扩展

利用 shortcode_atts_{$shortcode} 钩子让其他开发者可以修改属性:

function extendable_shortcode( $atts ) {
    $defaults = array(
        'color'  => 'blue',
        'size'   => 'medium',
        'style'  => 'default'
    );

    // 基础合并
    $atts = shortcode_atts( $defaults, $atts, 'extendable' );

    // 应用过滤钩子(第三方可以通过此钩子修改属性)
    $atts = apply_filters( 'extendable_shortcode_atts', $atts );

    // 确保过滤后仍然有必需的值
    $atts = wp_parse_args( $atts, $defaults );

    // ... 短代码逻辑
}

代码可维护性

统一属性前缀

为插件/主题的短代码属性添加前缀,避免冲突:

function prefixed_shortcode( $atts ) {
    $defaults = array(
        'myplugin_title'   => '',
        'myplugin_layout'  => 'grid',
        'myplugin_columns' => 3
    );

    $atts = shortcode_atts( $defaults, $atts, 'myplugin_widget' );

    // 使用更友好的内部变量名
    $title   = $atts['myplugin_title'];
    $layout  = $atts['myplugin_layout'];
    $columns = $atts['myplugin_columns'];

    // ... 逻辑处理
}

属性文档与类型提示

为复杂短代码添加详细的属性文档:

/**
 * 产品展示短代码
 *
 * 属性:
 * @param array $atts {
 *     短代码属性配置
 *
 *     @type int    $product_id   产品ID,默认0(显示最新产品)
 *     @type string $display_type 显示类型:'full'|'compact'|'thumb',默认'full'
 *     @type bool   $show_price   显示价格:1|0,默认1
 *     @type string $align        对齐方式:'left'|'center'|'right',默认'left'
 * }
 * @param string $content 短代码内容(本短代码不支持内容)
 */
function product_display_shortcode( $atts, $content = null ) {
    // 属性定义与处理
    $atts = shortcode_atts(
        array(
            'product_id'   => 0,
            'display_type' => 'full',
            'show_price'   => 1,
            'align'        => 'left'
        ),
        $atts,
        'product_display'
    );

    // 类型转换与验证
    $atts['product_id'] = absint( $atts['product_id'] );
    $atts['show_price'] = (bool) $atts['show_price'];

    $allowed_types = array( 'full', 'compact', 'thumb' );
    if ( ! in_array( $atts['display_type'], $allowed_types, true ) ) {
        $atts['display_type'] = 'full';
    }

    // ... 实现逻辑
}

与现代 WordPress 结合

与区块编辑器结合

为短代码创建对应的区块:

/**
 * 为短代码注册区块类型
 */
function register_shortcode_block() {
    register_block_type( 'my-plugin/shortcode-block', array(
        'attributes' => array(
            'title' => array(
                'type'    => 'string',
                'default' => '默认标题'
            ),
            'count' => array(
                'type'    => 'number',
                'default' => 5
            )
        ),
        'render_callback' => function( $attributes ) {
            // 将区块属性转换为短代码属性格式
            $shortcode_atts = array();
            foreach ( $attributes as $key => $value ) {
                $shortcode_atts[ $key ] = $value;
            }

            // 调用短代码函数
            return my_shortcode_callback( $shortcode_atts );
        }
    ) );
}
add_action( 'init', 'register_shortcode_block' );

REST API 端点中的属性处理

在自定义 REST API 端点中使用相似的逻辑:

register_rest_route( 'myplugin/v1', '/shortcode-render', array(
    'methods'  => 'POST',
    'callback' => function( WP_REST_Request $request ) {
        // 从请求中获取参数
        $provided_params = $request->get_params();

        // 使用类似 shortcode_atts 的逻辑
        $defaults = array(
            'title' => '',
            'count' => 10,
            'type'  => 'post'
        );

        // 合并并过滤参数
        $atts = array_intersect_key( $provided_params, $defaults );
        $atts = wp_parse_args( $atts, $defaults );

        // 验证和清理
        $atts['count'] = absint( $atts['count'] );
        $atts['title'] = sanitize_text_field( $atts['title'] );

        // 调用短代码渲染
        return array(
            'html' => my_shortcode_callback( $atts ),
            'atts' => $atts
        );
    },
    'permission_callback' => '__return_true'
) );

通过遵循这些最佳实践,你可以创建出安全、高效且易于维护的 WordPress 短代码,同时为未来的扩展和与现代 WordPress 功能的集成做好准备。