搜索全站
报错排查 14 分钟阅读

WordPress 短代码 Shortcode 开发:从入门到参数与嵌套

短代码是把复杂功能封装成一个 `[xxx]` 标签的最佳方式。本文讲清 Shortcode API 的工作机制、带参数与内容的两种写法、嵌套与转义规则、输出缓冲的正确用法,以及短代码不生效的六个原因。

U
UFO_PF 2024-07-12 · 阅读 14 分钟
Wo 报错排查
阅读提示
本文约 5,310 字,预计阅读 14 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:让编辑自己插入复杂模块

运营要在文章中间插一个”三栏特性对比表”,或者一个带数据的统计卡片。让他写 HTML 不现实,复制粘贴几次之后样式就乱了。

短代码(Shortcode)就是为这个场景设计的:开发者把 HTML+逻辑封装成 [feature title="xxx"]内容[/feature],编辑只要记住这一个标签。

它也是插件生态的基础——WooCommerce、Contact Form 7、绝大部分页面增强插件,核心交互方式都是短代码。

原理分析:短代码是怎么被解析的

WordPress 在输出正文时执行 do_shortcode()(挂在 the_content 过滤器上),流程:

  1. 用正则扫描正文,找出所有已注册的短代码标签;
  2. 按注册顺序逐个调用对应的回调函数;
  3. 把回调的返回值替换掉原标签位置。

关键点有三个:

关键点一:回调必须 return,不能 echo。

// 错误:直接 echo 会导致内容跑到文章开头
add_shortcode( 'hello', function() {
    echo '<p>Hello</p>';
} );

// 正确
add_shortcode( 'hello', function() {
    return '<p>Hello</p>';
} );

这是新手最常犯的错。因为 do_shortcode 是在过滤器里执行的,echo 会在内容还没输出时就打印出来。

关键点二:短代码只在执行了 do_shortcode 的地方生效。

默认 the_content(正文)会执行,但以下地方默认不执行:

  • 摘要 the_excerpt
  • 小工具文本 widget_text
  • 自定义字段的输出
  • 主题的某些自定义区域

要开启:

add_filter( 'the_excerpt', 'do_shortcode' );
add_filter( 'widget_text', 'do_shortcode' );
add_filter( 'term_description', 'do_shortcode' );

关键点三:属性名会被转成小写。

[box Title="x"] 在回调里收到的是 $atts['title'],不是 Title。

保姆级实操

一、最简形式

add_shortcode( 'mmf_year', function() {
    return date( 'Y' );
} );

用法:[mmf_year],输出 2026。

二、带参数(自闭合短代码)

add_shortcode( 'mmf_box', function( $atts ) {
    $atts = shortcode_atts( array(
        'title' => '提示',
        'type'  => 'info',      // info / warning / danger
        'icon'  => '',
    ), $atts, 'mmf_box' );

    $type = sanitize_html_class( $atts['type'] );

    return sprintf(
        '<div class="mmf-box mmf-box-%1$s">%2$s<div class="mmf-box-body"><strong>%3$s</strong></div></div>',
        esc_attr( $type ),
        $atts['icon'] ? '<span class="mmf-box-icon">' . esc_html( $atts['icon'] ) . '</span>' : '',
        esc_html( $atts['title'] )
    );
} );

用法:[mmf_box type="warning" icon="⚠" title="注意备份"]

shortcode_atts() 的三个参数:默认值数组、用户传入的数组、短代码名(第三个参数用于 shortcode_atts_{$shortcode} 过滤器,让别人能改默认值)。

三、带内容的短代码(闭合式)

add_shortcode( 'mmf_panel', function( $atts, $content = null ) {
    $atts = shortcode_atts( array(
        'title' => '',
    ), $atts, 'mmf_panel' );

    if ( null === $content ) {
        return '';
    }

    // 关键:对内容再执行一次 do_shortcode,支持嵌套
    $content = do_shortcode( shortcode_unautop( $content ) );

    return sprintf(
        '<div class="mmf-panel"><h4>%s</h4><div class="mmf-panel-body">%s</div></div>',
        esc_html( $atts['title'] ),
        $content
    );
} );

用法:

[mmf_panel title="操作步骤"]
1. 备份数据库
2. 修改配置
[mmf_box type="danger" title="别在生产环境直接改"]
[/mmf_panel]

嵌套的两个必要处理:

  • do_shortcode( $content ):让内容里的内层短代码也被解析;
  • shortcode_unautop():去掉 WordPress 自动加的 <p> 和 <br>,否则短代码里会出现莫名其妙的空段落。

四、输出缓冲(复杂 HTML 时更好读)

add_shortcode( 'mmf_recent', function( $atts ) {
    $atts = shortcode_atts( array(
        'num'  => 5,
        'cat'  => '',
    ), $atts, 'mmf_recent' );

    $args = array(
        'posts_per_page' => absint( $atts['num'] ),
        'post_status'    => 'publish',
    );
    if ( $atts['cat'] ) {
        $args['cat'] = absint( $atts['cat'] );
    }
    $q = new WP_Query( $args );
    if ( ! $q->have_posts() ) {
        return '';
    }

    ob_start();
    echo '<ul class="mmf-recent">';
    while ( $q->have_posts() ) {
        $q->the_post();
        printf(
            '<li><a href="%s">%s</a><span class="date">%s</span></li>',
            esc_url( get_permalink() ),
            esc_html( get_the_title() ),
            esc_html( get_the_date( 'Y-m-d' ) )
        );
    }
    echo '</ul>';
    wp_reset_postdata();
    return ob_get_clean();
} );

ob_start() + ob_get_clean() 是写长 HTML 的标准做法,比字符串拼接可读性好得多。别忘了 wp_reset_postdata()。

五、转义规则(安全)

短代码内容里如果用户要写 HTML,需要正确处理:

$content = wp_kses_post( $content );   // 允许文章级别的 HTML(a, strong, em 等)
$content = do_shortcode( $content );   // 再解析嵌套短代码

千万别 strip_tags()(会清掉合法格式),也别完全不处理(XSS 风险)。

属性值一定要 esc_attr()、esc_html()、esc_url()。

六、在主题/模板里直接调用

echo do_shortcode( '[mmf_recent num="8"]' );

七、检查短代码是否已注册

if ( shortcode_exists( 'mmf_box' ) ) { ... }
wp eval 'global $shortcode_tags; print_r(array_keys($shortcode_tags));'

常见踩坑与失败案例

  1. 案例一:短代码输出跑到文章最前面。原因:回调里用了 echo 而不是 return。
  2. 案例二:短代码在小工具里显示成 [mmf_box] 原文。原因:widget_text 过滤器没挂 do_shortcode。加:

php
add_filter( 'widget_text', 'do_shortcode' );

  1. 嵌套短代码不被解析:外层回调忘了 do_shortcode( $content )。注意:同名短代码不能嵌套自己([mmf_panel][mmf_panel][/mmf_panel][/mmf_panel] 会解析失败,这是 WordPress 正则的限制)。
  2. 自动 <p> 破坏布局:WordPress 的 wpautop 会在短代码前后插入 <p>。用 shortcode_unautop() 或者在 the_content 之前注册短代码。也可以把 wpautop 延后:

php
remove_filter( 'the_content', 'wpautop' );
add_filter( 'the_content', 'wpautop', 12 ); // 放到 do_shortcode 之后

  1. 属性名大小写:[box Title="x"] 收到的是 $atts['title']。
  2. 案例三:短代码里做了数据库查询但没 wp_reset_postdata(),导致页面下方的”相关文章”全部显示成同一篇。这是最隐蔽的一类 bug。
  3. 参数里有空格:[mmf_box title="hello world"] 必须用引号包裹属性值,否则只收到 hello。
  4. 缓存问题:短代码输出动态内容(如当前时间、随机文章)时,页面缓存会让它”不更新”。要用 AJAX 或排除缓存。

总结

短代码开发记住五条:回调必须 return 不能 echo;用 shortcode_atts() 处理默认值;带内容的闭合式要 do_shortcode($content) + shortcode_unautop() 才支持嵌套且不会出现多余 <p>;复杂 HTML 用 ob_start() + ob_get_clean(),循环后一定 wp_reset_postdata();所有输出 esc_*() 转义。默认只在正文生效,要在摘要、小工具、自定义字段里用就得手动挂 do_shortcode 过滤器。另外同名短代码不能自嵌套,这是正则解析的硬限制。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。