剖析 WordPress `do_shortcode()` 函数的源码:如何解析和执行短代码,并支持嵌套。

各位观众老爷们,晚上好!我是今天的主讲人,江湖人称代码老司机。今天咱们不飙车,咱们聊聊 WordPress 里的一个“老司机”级的函数:do_shortcode()。这玩意儿,看似简单,实则蕴含着 WordPress 的灵魂,是理解 WordPress 如何处理内容的关键。

一、 短代码是个什么鬼?

首先,咱们得明白什么是短代码。如果你是 WordPress 老手,这部分可以直接跳过。但为了照顾新手,我还是啰嗦两句。

短代码,顾名思义,就是简短的代码。它允许你在文章、页面或者小工具中嵌入一些复杂的功能,而无需编写大量的 HTML、CSS 或 JavaScript。你可以把它想象成一个快捷方式,指向了一段预先定义好的代码。

举个栗子:

这个短代码,如果你直接写 HTML,那得写多少图片标签啊!但是用了短代码,一行搞定,WordPress 会自动把 ID 为 1,2,3,4,5 的图片展示成一个画廊。

二、 do_shortcode():短代码的“翻译官”

do_shortcode() 函数,就是 WordPress 负责“翻译”这些短代码的。它的作用很简单:扫描一段文本,找到所有的短代码,然后执行它们对应的函数,最后把执行结果替换掉原来的短代码。

用人话来说,它就像一个翻译官,把 “” 翻译成了一堆 HTML 代码,然后替换掉原文中的短代码。

三、 源码剖析:do_shortcode() 的“内心世界”

好了,废话不多说,咱们直接上源码,看看 do_shortcode() 到底是怎么工作的。

首先,咱们找到 wp-includes/shortcodes.php 这个文件。这里面定义了 do_shortcode() 函数。

function do_shortcode( $content, $ignore_html = false ) {
    global $shortcode_tags;

    if ( false === strpos( $content, '[' ) ) {
        return $content;
    }

    if ( empty( $shortcode_tags ) || ! is_array( $shortcode_tags ) ) {
        return $content;
    }

    // 处理 HTML 编码实体,避免误解析
    $content = preg_replace_callback( '/<([^>]*?)(?:>|$)/', 'shortcode_split_html_callback', $content );

    // 处理短代码
    $pattern = get_shortcode_regex();
    $content = preg_replace_callback( "/$pattern/s", 'do_shortcode_tag', $content );

    //还原 HTML 编码实体
    $content = preg_replace_callback( '/[/', 'shortcode_normalize_callback', $content );
    $content = preg_replace_callback( '/]/', 'shortcode_normalize_callback', $content );

    return $content;
}

咱们一步一步来分析:

  1. 初步判断:

    if ( false === strpos( $content, '[' ) ) {
        return $content;
    }
    
    if ( empty( $shortcode_tags ) || ! is_array( $shortcode_tags ) ) {
        return $content;
    }

    这两个 if 语句是用来做初步判断的。第一个判断是:如果内容中没有 [ 字符,那肯定没有短代码,直接返回原始内容。第二个判断是:如果 $shortcode_tags 变量为空或者不是一个数组,那说明没有注册任何短代码,也直接返回原始内容。

    $shortcode_tags 是一个全局变量,用来存储所有注册的短代码及其对应的处理函数。

  2. 处理 HTML 编码实体

    $content = preg_replace_callback( '/<([^>]*?)(?:>|$)/', 'shortcode_split_html_callback', $content );

    这行代码使用正则表达式和回调函数 shortcode_split_html_callback 来处理 HTML 编码实体。 这样做是为了防止 HTML 标签中的尖括号干扰短代码的解析。 shortcode_split_html_callback 函数会将 HTML 标签中的尖括号替换为编码实体,例如 &lt;&gt;,以便短代码解析器不会错误地将它们识别为短代码的一部分。

  3. 正则匹配:

    $pattern = get_shortcode_regex();
    $content = preg_replace_callback( "/$pattern/s", 'do_shortcode_tag', $content );

    这才是核心代码。首先,get_shortcode_regex() 函数用来生成一个匹配短代码的正则表达式。然后,preg_replace_callback() 函数使用这个正则表达式来匹配内容中的所有短代码,并对每个匹配到的短代码调用 do_shortcode_tag() 函数进行处理。

    get_shortcode_regex() 函数的代码如下:

    function get_shortcode_regex() {
        global $shortcode_tags;
        $tagnames = array_keys( $shortcode_tags );
        $tagregexp = join( '|', array_map('preg_quote', $tagnames) );
    
        // WARNING! Do not change this regex without changing do_shortcode_tag() and strip_shortcodes()
        // Also, see shortcode_unautop() and shortcode.js.
        return
            '\['                              // Opening bracket
            . '(\[?)'                           // 1: Optional second opening bracket for escaping shortcodes: [[tag]]
            . "($tagregexp)"                     // 2: Shortcode name
            . '(?![\w-])'                       // Word boundary
            . '('                                // 3: Unroll the attributes
            .     '\s*'
            .     '([^\/?>]+)'                   // Attributes
            .     '\s*'
            . ')?'
            . '(\/?)'                           // 4: Optional closing slash
            . '\]'                              // Closing bracket
            . '(?:'
            .     '\['                              // Opening bracket
            .     '\/'                             // Slash for closing shortcode
            .     "($tagregexp)"                     // 5: Closing shortcode name
            .     '\]'                              // Closing bracket
            . ')?';                              // 6: Optional closing shortcode
    }

    这个函数的作用是根据已经注册的短代码,生成一个复杂的正则表达式。这个正则表达式可以匹配各种形式的短代码,包括自闭合的短代码([tag /])和带闭合标签的短代码([tag]content[/tag])。

    do_shortcode_tag() 函数的代码如下:

    function do_shortcode_tag( $matches ) {
        global $shortcode_tags;
    
        // Allow [[foo]] syntax for escaping a tag
        if ( $matches[1] == '[' && $matches[6] == ']' ) {
            return substr( $matches[0], 1, -1 );
        }
    
        $tag = $matches[2];
        $attr = shortcode_parse_atts( $matches[3] );
    
        if ( isset( $matches[5] ) ) {
            // enclosing tag
            return $matches[1] . call_user_func( $shortcode_tags[$tag], $attr, $matches[5], $tag ) . $matches[6];
        } else {
            // self-closing tag
            return $matches[1] . call_user_func( $shortcode_tags[$tag], $attr, null,  $tag ) . $matches[6];
        }
    }

    这个函数的作用是:

    • 判断是否是转义的短代码([[tag]]),如果是,则直接返回 [tag]
    • 获取短代码的标签名($tag)和属性($attr)。
    • 判断是否是闭合标签,如果是,则调用对应的处理函数,并传入属性和内容。如果不是,则调用对应的处理函数,并传入属性和 null 作为内容。

    shortcode_parse_atts() 函数的作用是解析短代码的属性。

    function shortcode_parse_atts( $text ) {
        $atts = array();
        $pattern = '/(w+)s*=s*"([^"]*)"(?:s|$)|(w+)s*=s*'([^']*)'(?:s|$)|(w+)s*=s*([^s'"]+)(?:s|$)|"([^"]*)"(?:s|$)|(S+)(?:s|$)/';
        $text = preg_replace( "/[x{00a0}x{200b}]+/u", " ", $text );
        if ( preg_match_all( $pattern, $text, $match, PREG_SET_ORDER ) ) {
            foreach ( $match as $m ) {
                if ( ! empty( $m[1] ) )
                    $atts[ strtolower( $m[1] ) ] = stripcslashes( $m[2] );
                elseif ( ! empty( $m[3] ) )
                    $atts[ strtolower( $m[3] ) ] = stripcslashes( $m[4] );
                elseif ( ! empty( $m[5] ) )
                    $atts[ strtolower( $m[5] ) ] = stripcslashes( $m[6] );
                elseif ( isset( $m[7] ) and $m[7] !== '' )
                    $atts[] = stripcslashes( $m[7] );
                elseif ( isset( $m[8] ) )
                    $atts[] = stripcslashes( $m[8] );
            }
        } else {
            $atts = ltrim( $text );
        }
        return $atts;
    }

    这个函数使用正则表达式来解析短代码的属性,并将属性存储在一个数组中。

  4. 还原 HTML 编码实体

    $content = preg_replace_callback( '/[/', 'shortcode_normalize_callback', $content );
    $content = preg_replace_callback( '/]/', 'shortcode_normalize_callback', $content );

    这两行代码将之前被替换的 HTML 编码实体还原回原始的尖括号。 shortcode_normalize_callback 函数简单地将编码实体替换回对应的字符。

四、 短代码的注册:add_shortcode()

光有 do_shortcode() 函数还不行,你还得告诉 WordPress 哪些短代码需要“翻译”,以及用什么函数来“翻译”。这就需要 add_shortcode() 函数了。

add_shortcode() 函数的作用是注册一个短代码,并将其与一个处理函数关联起来。

add_shortcode( string $tag, callable $callback ): bool
  • $tag:短代码的标签名,比如 gallery
  • $callback:处理函数,当遇到这个短代码时,会调用这个函数。

举个栗子:

function my_shortcode_handler( $atts, $content = null ) {
    $atts = shortcode_atts( array(
        'width' => '200',
        'height' => '200',
    ), $atts );

    $width = intval( $atts['width'] );
    $height = intval( $atts['height'] );

    $output = '<img src="http://example.com/image.jpg" width="' . $width . '" height="' . $height . '" />';

    return $output;
}

add_shortcode( 'my_image', 'my_shortcode_handler' );

这段代码注册了一个名为 my_image 的短代码。当你在文章中使用 [my_image width="300" height="400"] 时,WordPress 会调用 my_shortcode_handler() 函数,并将 widthheight 属性传递给它。

shortcode_atts() 函数的作用是将用户传入的属性与默认属性合并。

五、 嵌套短代码:do_shortcode() 的递归调用

do_shortcode() 函数支持嵌套短代码。这意味着你可以在一个短代码的处理函数中再次调用 do_shortcode() 函数,从而实现短代码的嵌套。

举个栗子:

function outer_shortcode_handler( $atts, $content = null ) {
    $output = '<div>' . do_shortcode( $content ) . '</div>';
    return $output;
}

add_shortcode( 'outer', 'outer_shortcode_handler' );

function inner_shortcode_handler( $atts, $content = null ) {
    return '<span>Inner Content</span>';
}

add_shortcode( 'inner', 'inner_shortcode_handler' );

现在,如果你在文章中使用 [outer][inner][/inner][/outer],WordPress 会先调用 outer_shortcode_handler() 函数,然后在这个函数中调用 do_shortcode() 函数来处理 [inner][/inner] 短代码。最终的输出结果是 <div><span>Inner Content</span></div>

六、 总结:do_shortcode() 的核心流程

为了方便大家理解,我把 do_shortcode() 的核心流程总结成一个表格:

步骤 描述 涉及的函数
1 初步判断:检查内容中是否包含短代码,以及是否注册了短代码。 strpos(), empty(), is_array()
2 处理 HTML 编码实体:防止 HTML 标签中的尖括号干扰短代码的解析。 preg_replace_callback(), shortcode_split_html_callback()
3 正则匹配:使用正则表达式匹配内容中的所有短代码。 get_shortcode_regex(), preg_replace_callback()
4 处理每个匹配到的短代码: do_shortcode_tag()
4.1 判断是否是转义的短代码,如果是,则直接返回。
4.2 解析短代码的属性。 shortcode_parse_atts()
4.3 调用短代码对应的处理函数,并传入属性和内容。 call_user_func()
5 还原 HTML 编码实体:将之前被替换的 HTML 编码实体还原回原始的尖括号。 preg_replace_callback(), shortcode_normalize_callback()
6 返回处理后的内容。

七、 注意事项

  • 性能问题: do_shortcode() 函数使用正则表达式来匹配短代码,这可能会对性能产生影响。如果你的网站有很多短代码,或者内容很长,可以考虑使用缓存来提高性能。
  • 安全问题: 短代码的处理函数可以执行任意代码,因此需要谨慎处理用户输入的属性,避免安全漏洞。
  • 短代码的顺序: 短代码的执行顺序是不确定的,因此不要依赖短代码的执行顺序。

八、 总结

do_shortcode() 函数是 WordPress 的一个核心函数,它允许你在文章、页面或者小工具中嵌入复杂的功能,而无需编写大量的 HTML、CSS 或 JavaScript。理解 do_shortcode() 函数的工作原理,可以帮助你更好地理解 WordPress 的内容处理机制,并编写出更高效、更安全的短代码。

希望今天的讲座对大家有所帮助。如果有任何问题,欢迎在评论区留言。咱们下期再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注