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

WordPress 短代码的秘密:一次从源码到应用的深度剖析

大家好,我是老码农,今天咱们来聊聊 WordPress 短代码这个小可爱。别看它名字简单,肚子里可是藏着不少乾坤。今天咱们就把它扒个精光,从源码层面彻底搞懂它,保证以后你也能玩转短代码,让你的 WordPress 网站更加炫酷。

短代码是个啥?

想象一下,你想要在文章里插入一个复杂的 HTML 代码块,比如一个漂亮的按钮、一个嵌入的视频,或者一个动态的表格。如果每次都手动敲代码,那简直是噩梦。这时候,短代码就闪亮登场了!

短代码就像一个简短的“暗号”,你只需要在文章里输入这个暗号,WordPress 就会自动把它替换成你预先定义好的 HTML 代码。比如,你可以定义一个 [button color="red"]Click Me![/button] 短代码,然后在文章里使用它,WordPress 就会把它替换成一个红色的按钮。是不是很方便?

do_shortcode():短代码的核心引擎

好了,废话不多说,直接进入主题。do_shortcode() 函数是 WordPress 处理短代码的核心引擎,它负责解析文章内容中的短代码,并执行相应的回调函数,最终生成替换后的 HTML 代码。

我们先来看看 do_shortcode() 函数的源码(WordPress 6.4.3):

/**
 * Search content for shortcodes and filter shortcodes through their hooks.
 *
 * If there are no shortcodes defined, then the content will be returned
 * without any filtering. This might cause issues when plugins are disabled but
 * the shortcodes are still in the content.
 *
 * @since 2.5.0
 *
 * @global array $shortcode_tags
 *
 * @param string $content Content to search for shortcodes.
 * @return string Content with shortcodes filtered out.
 */
function do_shortcode( $content ) {
    global $shortcode_tags;

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

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

    // Always run wpautop last.
    if ( did_action( 'wp_loaded' ) && has_filter( 'the_content', 'wpautop' ) ) {
        $content = wpautop( $content );
    }

    return $content;
}

这段代码看起来有点长,但其实逻辑很简单:

  1. 检查短代码是否已定义: 首先,它会检查全局变量 $shortcode_tags 是否为空。这个变量存储了所有已注册的短代码及其对应的回调函数。如果 $shortcode_tags 为空,说明没有任何短代码被定义,那么 do_shortcode() 函数会直接返回原始内容,不做任何处理。
  2. 构建正则表达式: 如果 $shortcode_tags 不为空,do_shortcode() 函数会调用 get_shortcode_regex() 函数来构建一个用于匹配短代码的正则表达式。
  3. 使用正则表达式匹配并替换短代码: 使用 preg_replace_callback() 函数,将文章内容中所有匹配到的短代码都替换掉。preg_replace_callback() 函数的第二个参数是一个回调函数 do_shortcode_tag,它负责处理每一个匹配到的短代码。
  4. 运行 wpautop() 这是一个可选步骤,用于自动给文章内容添加 HTML 段落标签 <p> 和换行标签 <br>

接下来,我们重点看看 get_shortcode_regex() 函数和 do_shortcode_tag() 函数,它们才是短代码解析和执行的关键。

get_shortcode_regex():短代码的“雷达”

get_shortcode_regex() 函数负责生成一个用于匹配短代码的正则表达式。这个正则表达式能够匹配各种形式的短代码,包括:

  • 单个短代码:[shortcode]
  • 带属性的短代码:[shortcode attribute1="value1" attribute2="value2"]
  • 包含内容的短代码:[shortcode]content[/shortcode]
  • 自闭合短代码:[shortcode /]

我们来看看 get_shortcode_regex() 函数的源码:

/**
 * Retrieve the shortcode regex for searching.
 *
 * The regex searches for the following forms:
 *
 *  * `[shortcode /]`
 *  * `[shortcode foo="bar" /]`
 *  * `[shortcode]content[/shortcode]`
 *
 *  * `[shortcode foo="bar"]content[/shortcode]`
 *
 * @since 2.5.0
 *
 * @global array $shortcode_tags
 *
 * @return string The shortcode search 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 loop: Inside the opening shortcode tag
        .     '[^\]\/]*'                   // Not a closing bracket or forward slash
        .     '(?:'
        .         '\/(?!\])'               // A forward slash not followed by a closing bracket
        .         '[^\]\/]*'               // Not a closing bracket or forward slash
        .     ')*?'
        . ')'
        . '(?:'
        .     '(\/)'                        // 4: Self closing tag ...
        .     '\]'                          // ... and closing bracket
        . '|'
        .     '\]'                          // Closing bracket
        .     '(?:'
        .         '('                        // 5: Unroll the loop: Optionally, anything between the opening and closing shortcode tags
        .             '[^\[]*+'             // Not an opening bracket
        .             '(?:'
        .                 '\[(?!\/\2\])' // An opening bracket not followed by the closing shortcode tag
        .                 '[^\[]*+'         // Not an opening bracket
        .             ')*+'
        .         ')'
        .         '\[\/\2\]'             // Closing shortcode tag
        .     )?'
        . ')'
        . '(\]?)';                          // 6: Optional second closing brocket for escaping shortcodes: [[tag]]
}

代码有点长,但核心逻辑就是构建一个强大的正则表达式。

  1. 获取所有已注册的短代码标签名: 首先,它会从全局变量 $shortcode_tags 中获取所有已注册的短代码标签名,并将它们存储在一个数组中。
  2. 构建标签名的正则表达式: 然后,它会将这些标签名用 | 连接起来,并使用 preg_quote() 函数对它们进行转义,以确保它们在正则表达式中被正确地解释。
  3. 构建完整的正则表达式: 最后,它会将标签名的正则表达式嵌入到一个更大的正则表达式中,这个正则表达式可以匹配各种形式的短代码。

这个正则表达式非常复杂,如果你想深入了解它的细节,建议你参考相关的正则表达式教程。但总的来说,它的作用就是能够准确地找到文章内容中所有符合短代码格式的字符串。

do_shortcode_tag():短代码的“翻译官”

do_shortcode_tag() 函数是短代码解析和执行的关键。它接收 preg_replace_callback() 函数传递过来的匹配结果,并根据匹配到的短代码标签名,调用相应的回调函数,最终生成替换后的 HTML 代码。

我们来看看 do_shortcode_tag() 函数的源码:

/**
 * Regular Expression Callback for do_shortcode function.
 *
 * @since 2.5.0
 *
 * @global array $shortcode_tags
 *
 * @param array $matches The array of matches from the regular expression.
 * @return string|false False on failure, otherwise the content that will replace the shortcode.
 */
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 ( ! is_callable( $shortcode_tags[ $tag ] ) ) {
        return $matches[0];
    }

    $content = ! empty( $matches[5] ) ? $matches[5] : null;

    return $shortcode_tags[ $tag ]( $attr, $content, $tag );
}

这段代码的逻辑如下:

  1. 处理转义的短代码: 首先,它会检查是否是转义的短代码,比如 [[shortcode]]。如果是,它会直接返回 [shortcode],不做任何处理。
  2. 提取短代码标签名和属性: 然后,它会从匹配结果中提取短代码的标签名和属性。shortcode_parse_atts() 函数用于解析短代码的属性,将它们转换成一个关联数组。
  3. 检查回调函数是否可调用: 接下来,它会检查 $shortcode_tags 数组中是否存在与短代码标签名对应的回调函数,并且这个回调函数是否可调用。如果不存在或者不可调用,它会直接返回原始的短代码,不做任何处理。
  4. 提取短代码内容: 如果是包含内容的短代码,它会从匹配结果中提取短代码的内容。
  5. 调用回调函数: 最后,它会调用与短代码标签名对应的回调函数,并将属性数组、短代码内容和标签名作为参数传递给回调函数。回调函数会根据这些参数生成替换后的 HTML 代码,do_shortcode_tag() 函数会将这个 HTML 代码返回,最终替换掉文章内容中的短代码。

如何注册和使用短代码?

了解了 do_shortcode() 函数的原理之后,我们来看看如何在 WordPress 中注册和使用短代码。

注册短代码需要使用 add_shortcode() 函数。这个函数接收两个参数:短代码的标签名和对应的回调函数。

例如,我们可以注册一个名为 hello 的短代码:

function hello_shortcode( $atts, $content = null ) {
    return '你好,世界!';
}
add_shortcode( 'hello', 'hello_shortcode' );

这段代码定义了一个名为 hello_shortcode() 的回调函数,它返回一个简单的字符串 "你好,世界!"。然后,我们使用 add_shortcode() 函数将 hello 短代码与 hello_shortcode() 函数关联起来。

现在,你可以在文章中使用 [hello] 短代码了,WordPress 会自动将其替换成 "你好,世界!"。

短代码的属性

短代码可以带属性,属性可以用来控制短代码的行为。例如,我们可以定义一个带 name 属性的 hello 短代码:

function hello_shortcode( $atts, $content = null ) {
    $atts = shortcode_atts(
        array(
            'name' => '朋友',
        ),
        $atts,
        'hello'
    );

    return '你好,' . esc_html( $atts['name'] ) . '!';
}
add_shortcode( 'hello', 'hello_shortcode' );

这段代码使用了 shortcode_atts() 函数来定义短代码的默认属性。shortcode_atts() 函数接收三个参数:

  • 默认属性数组: 定义了短代码的默认属性和值。
  • 用户传递的属性数组: 包含了用户在文章中使用的短代码的属性和值。
  • 短代码标签名: 用于过滤属性,防止属性冲突。

shortcode_atts() 函数会将用户传递的属性与默认属性合并,如果用户没有传递某个属性,则使用默认值。

现在,你可以在文章中使用 [hello name="老码农"] 短代码了,WordPress 会自动将其替换成 "你好,老码农!"。

短代码的嵌套

短代码可以嵌套使用,这使得我们可以创建更复杂的短代码结构。例如,我们可以定义一个 box 短代码,它可以包含其他短代码:

function box_shortcode( $atts, $content = null ) {
    return '<div class="box">' . do_shortcode( $content ) . '</div>';
}
add_shortcode( 'box', 'box_shortcode' );

这段代码定义了一个 box_shortcode() 函数,它接收一个 content 参数,这个参数包含了 box 短代码的内容。然后,我们使用 do_shortcode() 函数对 content 进行处理,这样就可以解析 content 中包含的其他短代码。

现在,你可以在文章中使用以下短代码:

[box]
[hello name="老码农"]
[/box]

WordPress 会首先解析 hello 短代码,然后将解析后的结果传递给 box 短代码,最终生成一个包含 "你好,老码农!" 的 div 元素。

短代码的优先级

如果多个短代码的回调函数都注册到同一个标签名下,那么 WordPress 会按照注册的顺序依次执行这些回调函数。你可以使用 remove_shortcode() 函数来移除某个短代码的回调函数,或者使用 remove_all_shortcodes() 函数来移除所有短代码的回调函数。

注意事项

  • 安全: 在使用短代码时,一定要注意安全问题。避免在短代码的回调函数中使用未经转义的用户输入,以防止 XSS 攻击。
  • 性能: 短代码的解析和执行会消耗一定的性能,因此应该避免在同一个页面中使用过多的短代码。
  • 可维护性: 尽量将短代码的回调函数定义在单独的文件中,并使用清晰的命名规范,以提高代码的可维护性。

总结

今天我们深入剖析了 WordPress 短代码的原理和使用方法。我们学习了 do_shortcode() 函数的源码,了解了它是如何解析和执行短代码的。我们还学习了如何注册和使用短代码,以及如何处理短代码的属性和嵌套。

希望通过今天的讲解,你能够对 WordPress 短代码有更深入的理解,并能够灵活运用它来构建更强大的 WordPress 网站。

最后,记住一点,代码是死的,人是活的。理解了原理,才能灵活运用,才能创造出更多有趣的东西。 好了,今天的讲座就到这里,谢谢大家!

发表回复

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