各位代码界的探险家们,早上好!今天咱们要深入WordPress的心脏地带,一起扒一扒do_shortcode()
这个神奇的函数,看看它是如何像一位优秀的魔术师一样,把那些看似简单的短代码变成功能强大的魔法。准备好了吗?让我们开始这场代码考古之旅!
一、短代码的起源故事:为什么要搞短代码?
想象一下,你是一位WordPress博主,想在文章里插入一个漂亮的相册,或者一个复杂的表格。如果让你每次都手动写HTML代码,那简直是场噩梦!于是,短代码应运而生。它们就像是一些预定义的“快捷方式”,用简单的标签包裹起来,让你可以轻松地插入复杂的功能,而无需编写大量的HTML或PHP代码。
例如:
[my_gallery ids="1,2,3,4,5"]
这段短短的代码,可能背后藏着一个完整的相册功能!
二、do_shortcode()
:短代码的“翻译器”
do_shortcode()
函数是WordPress短代码机制的核心。它的主要任务就是:
- 扫描: 在给定的字符串中查找短代码。
- 解析: 提取短代码的标签和属性。
- 执行: 调用与该标签关联的函数(也就是短代码的回调函数)。
- 替换: 将短代码替换为回调函数的返回值。
简单来说,它就像一位勤劳的翻译官,把那些神秘的短代码“翻译”成浏览器能够理解的HTML代码。
三、源码剖析: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;
}
// Avoid nesting this function when already being processed.
static $doing_shortcode_field;
if ( true === $doing_shortcode_field ) {
return $content;
}
$doing_shortcode_field = true;
//preg_match_all( '@[([^<>&/[]x00-x20=]++)@', $content, $matches, PREG_OFFSET_CAPTURE );
$pattern = get_shortcode_regex();
$content = preg_replace_callback( "/$pattern/s", 'do_shortcode_tag', $content );
// Always restore doing_shortcode_field, regardless.
$doing_shortcode_field = false;
return $content;
}
看起来是不是有点复杂?别怕,咱们一步步来拆解:
-
global $shortcode_tags;
: 这一行代码非常重要。$shortcode_tags
是一个全局数组,它存储了所有已注册的短代码及其对应的回调函数。 也就是说,WordPress就是通过这个数组来知道哪个短代码对应哪个函数。 -
if ( false === strpos( $content, '[' ) ) { return $content; }
: 这是一个快速检查。如果内容中根本没有[
,那就说明没有短代码,直接返回原始内容,避免不必要的处理。 -
if ( empty( $shortcode_tags ) || ! is_array( $shortcode_tags ) ) { return $content; }
: 再次检查,确保$shortcode_tags
数组存在并且不为空。如果没有注册任何短代码,也直接返回。 -
static $doing_shortcode_field;
: 这是一个静态变量,用于防止短代码的无限嵌套调用。如果do_shortcode()
函数正在执行中,再次调用它会直接返回,避免栈溢出。 -
$pattern = get_shortcode_regex();
: 这是一个关键步骤。get_shortcode_regex()
函数会生成一个用于匹配短代码的正则表达式。这个正则表达式非常强大,可以匹配各种形式的短代码,包括带有属性的、自闭合的等等。咱们待会会详细分析这个函数。 -
$content = preg_replace_callback( "/$pattern/s", 'do_shortcode_tag', $content );
: 这才是真正的“翻译”过程!preg_replace_callback()
函数会使用正则表达式$pattern
在内容$content
中查找所有匹配的短代码,并对每个匹配到的短代码调用do_shortcode_tag()
函数进行处理。 -
$doing_shortcode_field = false;
: 处理完毕后,重置静态变量,允许后续的短代码处理。
四、get_shortcode_regex()
:短代码的“雷达”
让我们看看get_shortcode_regex()
函数是如何构建那个强大的正则表达式的。
function get_shortcode_regex( $tagnames = null ) {
global $shortcode_tags;
if ( empty( $tagnames ) ) {
$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]]
}
这个正则表达式看起来像一堆乱码,但其实它非常精妙。让我们来解释一下它的关键部分:
'\['
: 匹配开头的方括号[
。(\[?)
: 匹配可选的第二个开头的方括号,用于转义短代码,例如[[my_shortcode]]
。($tagregexp)
: 匹配短代码的标签名。$tagregexp
是一个由所有已注册的短代码标签名组成的正则表达式,例如(my_gallery|my_table|...)
。(?![\w-])
: 确保标签名后面不是字母、数字或下划线,这是一个词语边界的判断,避免误匹配。- *`([^]/] … )
**: 这部分匹配短代码的属性。它允许属性中包含除了
]和
/` 之外的任何字符。 (\/)\]
: 匹配自闭合的短代码,例如[my_shortcode /]
。\](?: ... )?
: 匹配闭合的短代码,例如[my_shortcode] content [/my_shortcode]
。- *`([^[]+(?:[(?!/2])[^[]+)+)`**: 匹配短代码的内容,允许内容中包含嵌套的短代码,但要排除与当前短代码标签相同的闭合标签。
\[\/\2\]
: 匹配闭合的短代码标签。(\]?)
: 匹配可选的第二个闭合的方括号,用于转义短代码,例如[[my_shortcode]]
。
这个正则表达式考虑了各种情况,包括:
- 普通短代码:
[my_shortcode]
- 带有属性的短代码:
[my_shortcode attribute1="value1" attribute2="value2"]
- 自闭合短代码:
[my_shortcode /]
- 带有内容的短代码:
[my_shortcode] content [/my_shortcode]
- 嵌套短代码:
[my_shortcode] content [another_shortcode] inner content [/another_shortcode] [/my_shortcode]
- 转义短代码:
[[my_shortcode]]
五、do_shortcode_tag()
:短代码的“执行者”
do_shortcode_tag()
函数是真正执行短代码的地方。它接收 preg_replace_callback()
函数匹配到的短代码信息,并调用相应的回调函数。
function do_shortcode_tag( $matches ) {
global $shortcode_tags;
// Allow shortcodes to be redefined.
$tag = $matches[2];
if ( isset( $shortcode_tags[ $tag ] ) ) {
$func = $shortcode_tags[ $tag ];
$atts = shortcode_parse_atts( $matches[3] );
if ( is_callable( $func ) ) {
$out = call_user_func( $func, $atts, ! empty( $matches[5] ) ? $matches[5] : null, $tag );
} else {
$out = '';
}
if ( ! empty( $out ) ) {
return $matches[1] . $out . $matches[6];
} else {
return $matches[0];
}
} else {
return $matches[0];
}
}
让我们逐行分析:
-
$tag = $matches[2];
: 从匹配结果中提取短代码的标签名。$matches[2]
对应于get_shortcode_regex()
函数中定义的第二个捕获组,也就是标签名。 -
if ( isset( $shortcode_tags[ $tag ] ) ) { ... }
: 检查该标签名是否已注册。 -
$func = $shortcode_tags[ $tag ];
: 获取与该标签名关联的回调函数。 -
$atts = shortcode_parse_atts( $matches[3] );
: 解析短代码的属性。$matches[3]
对应于get_shortcode_regex()
函数中定义的第三个捕获组,也就是属性字符串。shortcode_parse_atts()
函数会将属性字符串解析成一个关联数组,例如attribute1="value1" attribute2="value2"
会被解析成['attribute1' => 'value1', 'attribute2' => 'value2']
。 -
if ( is_callable( $func ) ) { ... }
: 确保回调函数是可调用的。 -
$out = call_user_func( $func, $atts, ! empty( $matches[5] ) ? $matches[5] : null, $tag );
: 调用回调函数,并将属性数组、内容(如果存在)和标签名作为参数传递给它。$matches[5]
对应于get_shortcode_regex()
函数中定义的第五个捕获组,也就是短代码的内容。 -
return $matches[1] . $out . $matches[6];
: 将短代码替换为回调函数的返回值。$matches[1]
和$matches[6]
对应于转义短代码时使用的可选方括号。
六、shortcode_parse_atts()
:属性的“分解者”
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 ( ! empty( $m[7] ) ) {
$atts[] = stripcslashes( $m[7] );
} elseif ( ! empty( $m[8] ) ) {
$atts[] = stripcslashes( $m[8] );
}
}
} else {
$atts = ltrim( $text );
}
return $atts;
}
这个函数使用正则表达式来匹配属性,支持以下几种形式:
attribute="value"
(双引号)attribute='value'
(单引号)attribute=value
(无引号)"value"
(匿名属性,值被添加到数组中)value
(匿名属性,值被添加到数组中)
七、短代码的注册与使用
要使用短代码,首先需要注册它。可以使用 add_shortcode()
函数来注册一个短代码及其对应的回调函数。
function my_shortcode_callback( $atts, $content = null, $tag = '' ) {
// 处理属性
$atts = shortcode_atts(
array(
'id' => 0,
'title' => 'Default Title',
),
$atts,
$tag
);
$id = intval( $atts['id'] );
$title = esc_attr( $atts['title'] );
// 构建输出
$output = '<div class="my-shortcode">';
$output .= '<h2>' . $title . '</h2>';
$output .= '<p>ID: ' . $id . '</p>';
if ( ! is_null( $content ) ) {
$output .= '<p>Content: ' . do_shortcode( $content ) . '</p>';
}
$output .= '</div>';
return $output;
}
add_shortcode( 'my_shortcode', 'my_shortcode_callback' );
这个例子注册了一个名为 my_shortcode
的短代码,它接受 id
和 title
两个属性,并输出一个带有标题和ID的 div
元素。 shortcode_atts()
函数用于设置属性的默认值。 注意,如果短代码有内容,并且内容中可能包含其他短代码,需要使用 do_shortcode( $content )
来处理内容。
然后在文章或页面中使用这个短代码:
[my_shortcode id="123" title="My Custom Title"]This is the content.[/my_shortcode]
八、嵌套短代码的处理
do_shortcode()
函数本身支持嵌套短代码。当它遇到一个短代码时,会递归地调用自身来处理嵌套的短代码。 这样就可以实现非常复杂的功能。
例如:
[outer_shortcode]
[inner_shortcode attribute="value"]Inner Content[/inner_shortcode]
[/outer_shortcode]
在这个例子中,do_shortcode()
函数会先处理 outer_shortcode
,然后在处理 outer_shortcode
的回调函数时,会再次调用 do_shortcode()
函数来处理 inner_shortcode
。
九、短代码的安全性
短代码非常方便,但也存在安全风险。如果允许用户随意使用短代码,可能会导致安全漏洞,例如跨站脚本攻击(XSS)。
因此,需要注意以下几点:
- 验证和转义属性: 确保属性值是安全的,可以使用
esc_attr()
、esc_url()
等函数来转义属性值。 - 限制短代码的使用: 只允许受信任的用户使用短代码。
- 避免执行敏感操作: 不要在短代码的回调函数中执行敏感操作,例如数据库修改或文件操作。
十、总结与展望
do_shortcode()
函数是WordPress短代码机制的核心,它通过正则表达式匹配短代码,解析属性,并调用相应的回调函数来执行短代码。 短代码机制非常灵活,可以用于实现各种复杂的功能,但同时也需要注意安全风险。
希望今天的讲座能够帮助你更深入地理解WordPress的短代码机制。 掌握了这些知识,你就可以像一位真正的代码魔术师一样,创造出各种神奇的WordPress功能! 记住,代码的世界充满无限可能,勇敢地去探索吧!
今天的课程就到这里,各位探险家们,下次再见!