搜索全站
主题 17 分钟阅读

换主题后短代码残留:批量清理失效短代码的完整方案

换主题后文章里冒出 [row]、[button] 这类方括号,是短代码失效的典型表现。本文给出检测、兜底、批量替换与彻底清理四步方案,含 SQL 定位、WP-CLI 替换与兼容层代码。

U
UFO_PF 2017-03-02 · 阅读 17 分钟
换主 主题
阅读提示
本文约 6,642 字,预计阅读 17 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:正文里那堆方括号,其实就是失效的短代码

换主题后访客最先发现的异常,往往是正文里凭空多出一串方括号:[row][col size="6"]产品介绍[/col][/row]。站长在编辑器里看不到任何异常,因为编辑器里这块内容是通过旧主题的短代码渲染的,现在渲染逻辑没了,只剩原始标记被当成纯文本输出。

这类残留的麻烦之处在于分布广、形态杂。一个用了五年的站点,短代码可能出现在三种地方:正文 post_content、自定义字段 postmeta(尤其是页面构建器留下的)、以及小工具文本。而且同一套短代码在不同时期的写法还不一样——早期是 [col size=6](不带引号),后来变成 [col size="6"],再后来加了 [col size="6" class="mt-2"]。想用一条简单的替换语句清干净,几乎不可能。

处理顺序上有个重要原则:先兜底、再清理。兜底是让站点立刻恢复正常显示,清理是长期把内容里的历史包袱去掉。跳过兜底直接清理,风险极高——一旦替换语句写错,正文就永久损坏了。

原理分析:短代码是怎么被解析的,失效后为什么变明文

WordPress 在输出 the_content() 时,会经过 do_shortcode() 过滤器。它的工作流程是:用 get_shortcode_regex() 生成一条正则,扫描正文里所有形如 [name attr="v"]...[/name] 的结构;对每个命中项,去全局的 $shortcode_tags 里查找 name 对应的回调函数;找到了就调用回调替换成 HTML,找不到就原样返回。

所以「失效」的表现分两种:

情况 表现 原因
短代码未注册 [row] 方括号原样显示 $shortcode_tags 里没有 row
回调返回空 方括号消失,内容也消失 回调存在但逻辑依赖旧主题
嵌套未递归 外层正常,内层 [col] 明文 回调里没调用 do_shortcode( $content )
属性名变化 显示出来了但参数没生效 新回调的 shortcode_atts 字段不同

第二种最危险——它不发警告,内容静默消失。第三种最常见,因为很多主题作者写短代码时忘了递归处理嵌套内容。

另外要区分两类短代码的归属:WordPress 核心自带的(、、、、、)换任何主题都还在;插件提供的([contact-form-7 id="123"]、WooCommerce 的 [products])只要插件还在就还在;主题定义的([row]、[col]、[button]、[testimonial])换主题即失效。清理的目标只针对第三类,前两类千万别动——误删 [contact-form-7] 会让所有表单消失。

保姆级实操

步骤 1:先全量盘点到用了哪些短代码

# 用 WP-CLI 把正文里出现的所有方括号标签抓出来(需 MySQL 8 支持 REGEXP_SUBSTR)
wp db query "
SELECT REGEXP_SUBSTR(post_content, '\\\\[[a-zA-Z0-9_-]+') AS tag, COUNT(*) AS cnt
FROM wp_posts
WHERE post_status IN ('publish','draft','private')
  AND post_content LIKE '%[%'
GROUP BY tag ORDER BY cnt DESC LIMIT 50;" --skip-column-names

没有 WP-CLI 时,用 PHP 借助核心自带的正则函数一次性统计,准确率更高:

<?php
// 放在主题临时执行,或用 wp eval-file 运行
global $wpdb;
$rows = $wpdb->get_col(
    "SELECT post_content FROM {$wpdb->posts}
     WHERE post_status IN ('publish','draft','private')
       AND post_content LIKE '%[%'"
);

$found = array();
foreach ( $rows as $content ) {
    if ( preg_match_all( '/' . get_shortcode_regex() . '/s', $content, $m ) ) {
        foreach ( $m[2] as $tag ) {
            $found[ $tag ] = ( $found[ $tag ] ?? 0 ) + 1;
        }
    }
}
arsort( $found );
foreach ( $found as $tag => $cnt ) {
    $registered = shortcode_exists( $tag ) ? '已注册' : '未注册(失效)';
    printf( "%-24s %-14s %d\n", '[' . $tag . ']', $registered, $cnt );
}

get_shortcode_regex() 是核心函数,用它能保证和 do_shortcode() 用同一套规则,比手写正则可靠得多。输出里标「未注册」的就是要处理的对象。

步骤 2:先做兼容层兜底(不要直接改内容)

在插件或新主题里注册同名短代码,把旧标记翻译成新主题的 HTML。这一步让站点立刻恢复正常,且完全不触碰数据库:

<?php
/**
 * Plugin Name: 旧主题短代码兼容层
 * Description: 兼容历史 [row] [col] [button] 短代码,便于后续平滑清理
 */

add_shortcode( 'row', 'mmf_legacy_row' );
function mmf_legacy_row( $atts, $content = null ) {
    return '<div class="mmf-grid">' . do_shortcode( $content ) . '</div>';
}

add_shortcode( 'col', 'mmf_legacy_col' );
function mmf_legacy_col( $atts, $content = null ) {
    $atts = shortcode_atts( array(
        'size'  => '12',
        'class' => '',
    ), $atts, 'col' );

    $size  = absint( $atts['size'] );
    $class = trim( preg_replace( '/[^a-z0-9_\- ]/i', '', $atts['class'] ) );

    return sprintf(
        '<div class="mmf-grid__col--%d %s">%s</div>',
        $size,
        esc_attr( $class ),
        do_shortcode( $content )
    );
}

add_shortcode( 'testimonial', 'mmf_legacy_testimonial' );
function mmf_legacy_testimonial( $atts, $content = null ) {
    $atts = shortcode_atts( array( 'name' => '' ), $atts, 'testimonial' );
    return sprintf(
        '<blockquote class="mmf-quote"><p>%s</p><cite>%s</cite></blockquote>',
        do_shortcode( $content ),
        esc_html( $atts['name'] )
    );
}

注意每个回调都必须对 $content 再调用一次 do_shortcode(),否则嵌套结构会残留。

步骤 3:确认兼容层稳定后,再做批量替换

替换前必须完整备份,并用 --dry-run 先看影响范围:

# 备份
wp db export /tmp/before-shortcode-cleanup.sql

# 干跑:统计会命中多少条
wp search-replace '[row]' '' --include-columns=post_content --dry-run
wp search-replace '[/row]' '' --include-columns=post_content --dry-run

确认数量合理后再执行。带属性的短代码无法用 search-replace 处理,需要写脚本:

<?php
// 用 wp eval-file cleanup.php 执行,务必先备份
global $wpdb;

$posts = $wpdb->get_results(
    "SELECT ID, post_content FROM {$wpdb->posts}
     WHERE post_content LIKE '%[col %'
        OR post_content LIKE '%[/col]%'"
);

$changed = 0;
foreach ( $posts as $post ) {
    $new = preg_replace_callback(
        '/\[col([^\]]*)\](.*?)\[\/col\]/s',
        function( $m ) {
            // 抽出 size 属性,转成新主题的 div
            preg_match( '/size=["\']?(\d+)["\']?/', $m[1], $size );
            $n = isset( $size[1] ) ? $size[1] : '12';
            return sprintf(
                '<div class="mmf-grid__col--%d">%s</div>',
                (int) $n,
                $m[2]
            );
        },
        $post->post_content
    );

    if ( $new !== $post->post_content ) {
        wp_update_post( array(
            'ID'           => $post->ID,
            'post_content' => $new,
        ) );
        $changed++;
    }
}

printf( "已清理 %d 篇文章中的 col 短代码\n", $changed );

wp_update_post() 会触发修订版本与缓存刷新,比直接写 SQL 安全。数据量特别大时(上万篇)才考虑直接 SQL 更新,且要同步处理 post_modified 与缓存。

步骤 4:清理自定义字段与小工具里的残留

-- 查看自定义字段里的短代码残留
SELECT post_id, meta_key
FROM wp_postmeta
WHERE meta_value LIKE '%[row]%'
   OR meta_value LIKE '%[/col]%'
LIMIT 50;

小工具(WordPress 5.8 之后是区块)内容存在 wp_posts 里 post_type = 'wp_block' 或 wp_global_styles,也可能在 wp_options 的 widget_* 记录里,需要单独检查:

SELECT option_name FROM wp_options
WHERE option_value LIKE '%[row]%' AND option_name LIKE 'widget_%';

步骤 5:清完后把兼容层留一段时间再移除

兼容层的作用是「即使有漏网之鱼也不会显示成明文」。建议保留三个月,期间配合全站抓取(Screaming Frog 里搜索 [ 出现在正文的页面)确认零残留后再删除插件。

常见踩坑

  1. 用 strip_shortcodes() 直接剥掉:这个函数会把短代码连同包裹的内容一起删除,[button text="立即咨询"] 虽然没了括号,但按钮本身也消失了。
  2. 正则里忘了 s 修饰符:.*? 默认不匹配换行,跨行的嵌套短代码一个都匹配不到,清理后看起来「没变化」。
  3. 误伤核心与插件短代码:、、[contact-form-7] 必须排除,否则表单和相册全废。用 shortcode_exists() 做白名单过滤最稳。
  4. 直接改 SQL 却没清缓存:wp_update_post() 之外的写法要手动 clean_post_cache(),否则前台仍显示旧内容。
  5. 案例: 某站点用一条 UPDATE wp_posts SET post_content = REPLACE(post_content, '[row]', '') 清理,结果发现带属性变体的 [row class="full"] 一个都没清掉,正文里仍然残留方括号;更糟的是另一条语句把 [column](页面构建器的短代码,当时插件仍在用)也替换了,导致 200 多篇页面的三栏布局塌陷成单栏。教训:替换前必须先用 shortcode_exists() 把仍在生效的短代码排除,且属性变体必须用正则而非字符串替换。
  6. 忽略了修订版本:wp_posts 里 post_type = 'revision' 的记录也含有旧短代码,虽然不影响前台,但会干扰统计。统计时加 AND post_type = 'post' 条件。
  7. 清理后没重新生成站点地图:内容变了要触发一次 sitemap 更新,便于搜索引擎尽快重新抓取。

总结

短代码残留的处理顺序固定为:盘点(用 get_shortcode_regex() + shortcode_exists() 区分失效与生效)→ 兼容层兜底(注册同名短代码,回调内递归 do_shortcode())→ 备份后批量替换(简单形式用 wp search-replace,带属性变体用正则脚本)→ 全站复查 → 三个月后移除兼容层。

判断标准:全站抓取结果里,正文出现未闭合方括号的页面数为零;且核心短代码与插件短代码一个没少。清理属于内容层手术,务必放在 主题迁移到新主题时内容保全方案 之后执行,确认 CPT 与字段都已平移完毕再动手。

U
UFO_PF作者

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

留下你的想法

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