各位观众老爷,早上好/下午好/晚上好!我是你们的老朋友,今天咱们来聊聊WordPress里一个相当实用,但又容易被忽略的小可爱——Shortcode API。
Shortcode API:化繁为简的魔法棒
你想想,咱在WordPress编辑器里,噼里啪啦敲了一堆HTML,CSS,甚至JS代码,就为了实现一个简单的功能,比如插入一个漂亮的按钮,或者展示一个动态的图库。这得多麻烦啊!而且,一旦主题换了,这些代码可能就得重新改一遍,简直是噩梦!
Shortcode API就是来拯救我们的!它就像一根魔法棒,能把复杂的功能封装成简单的标签(Shortcode),你只需要在文章或者页面里输入这些标签,就能轻松实现各种效果,而不用管背后的复杂逻辑。而且,Shortcode和主题是分离的,换主题也不怕!
今天咱们就来扒一扒add_shortcode()
和do_shortcode()
这两个核心函数的源码,看看WordPress是怎么实现这个魔法的。
add_shortcode()
:注册你的魔法标签
add_shortcode()
函数的作用很简单:就是把一个Shortcode标签和一个对应的处理函数关联起来。当我们使用这个标签时,WordPress就会调用这个处理函数,生成最终的内容。
先来看一下add_shortcode()
函数的定义(位于wp-includes/shortcodes.php
):
function add_shortcode( $tag, $callback ) {
global $shortcode_tags;
if ( is_callable( $callback ) ) {
$shortcode_tags[ $tag ] = $callback;
}
}
是不是超级简单?
$tag
: 这是你要注册的Shortcode标签的名字,比如'my_button'
。注意,标签名只能包含字母、数字和下划线,而且必须以字母开头。$callback
: 这是一个函数名或者一个类方法,它负责处理Shortcode标签,并返回最终的内容。
这个函数做了啥呢?它只是简单地检查一下$callback
是不是一个有效的可调用函数,如果是,就把它存到一个全局数组$shortcode_tags
里,key就是$tag
,value就是$callback
。
$shortcode_tags
是一个全局数组,存储着所有已注册的 Shortcode 标签和对应的处理函数。
举个栗子
假设我们要创建一个Shortcode,用来显示当前日期:
function my_date_shortcode( $atts, $content = null ) {
return date('Y-m-d');
}
add_shortcode( 'my_date', 'my_date_shortcode' );
这里,我们定义了一个名为my_date_shortcode
的函数,它返回当前的日期。然后,我们用add_shortcode()
函数把'my_date'
标签和my_date_shortcode
函数关联起来。
现在,你就可以在文章或者页面里输入[my_date]
,WordPress就会把它替换成当前的日期了。
do_shortcode()
:施展魔法,替换标签
do_shortcode()
函数才是真正施展魔法的地方。它负责扫描文章内容,找到所有的Shortcode标签,然后调用对应的处理函数,把标签替换成最终的内容。
do_shortcode()
函数的定义(位于wp-includes/shortcodes.php
):
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 the content is recursively filtered.
static $recursion_level = 0;
if ( $recursion_level > 3 ) {
return $content;
}
$recursion_level++;
// If the input is ALL html, return the input.
if ( $ignore_html && ! preg_match( '/<[^>]*>[/', $content ) ) {
return $content;
}
preg_match_all(
'/' . get_shortcode_regex() . '/',
$content,
$matches,
PREG_SET_ORDER
);
if ( empty( $matches ) ) {
$recursion_level--;
return $content;
}
$replace = array();
$process = true;
foreach ( $matches as $match ) {
if ( $match[1] == '[' && $match[6] == ']' ) {
$replace[ $match[0] ] = '';
continue;
}
if ( $match[1] != '[' ) {
$shortcode = $match[2];
if ( isset( $shortcode_tags[ $shortcode ] ) && is_callable( $shortcode_tags[ $shortcode ] ) ) {
$atts = shortcode_parse_atts( $match[3] );
if ( false === $atts ) {
$atts = array();
}
$output = call_user_func(
$shortcode_tags[ $shortcode ],
$atts,
$match[5],
$shortcode
);
if ( null === $output ) {
$output = '';
}
$replace[ $match[0] ] = $output;
} else {
$replace[ $match[0] ] = $match[0];
}
} else {
$replace[ $match[0] ] = substr( $match[0], 1, -1 );
}
}
$content = strtr( $content, $replace );
$recursion_level--;
return $content;
}
这段代码稍微有点长,但咱们一步一步来分析:
$content
: 这是要处理的文章内容,也就是包含Shortcode标签的字符串。$ignore_html
: 一个布尔值,表示是否忽略HTML标签。如果设置为true
,并且文章内容全部是HTML,那么do_shortcode()
函数会直接返回原始内容。
接下来,我们来看看do_shortcode()
函数的主要逻辑:
- 快速退出: 首先,函数会快速检查文章内容里是否包含
[
字符。如果没有,说明没有Shortcode标签,直接返回原始内容,省时省力。 - 检查Shortcode标签: 然后,函数会检查全局数组
$shortcode_tags
是否为空。如果为空,说明没有注册任何Shortcode标签,也直接返回原始内容。 - 避免递归: 为了防止无限递归,函数会用一个静态变量
$recursion_level
来记录递归的深度。如果递归深度超过3,就直接返回原始内容。 - 忽略HTML: 如果
$ignore_html
为true
,并且文章内容全部是HTML,就直接返回原始内容。 - 正则匹配: 这是最关键的一步。函数使用
preg_match_all()
函数,用一个正则表达式来匹配文章内容里的所有Shortcode标签。这个正则表达式是由get_shortcode_regex()
函数生成的。 - 循环处理: 循环遍历所有匹配到的Shortcode标签,然后根据标签名,调用对应的处理函数,把标签替换成最终的内容。
- 字符串替换: 最后,使用
strtr()
函数,把文章内容里的所有Shortcode标签替换成最终的内容。
重点解析
get_shortcode_regex()
: 这个函数负责生成匹配Shortcode标签的正则表达式。这个正则表达式相当复杂,但它的作用就是匹配各种格式的Shortcode标签,包括带属性的,带内容的,自闭合的等等。
function get_shortcode_regex( $tagnames = null ) {
global $shortcode_tags;
if ( empty( $tagnames ) ) {
$tagnames = array_keys( $shortcode_tags );
}
if ( empty( $tagnames ) ) {
return false;
}
$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]]
}
这个函数根据已经注册的Shortcode标签,生成一个正则表达式,用于匹配文章内容里的Shortcode标签。正则表达式的细节这里就不展开了,有兴趣的可以自行研究。
shortcode_parse_atts()
: 这个函数负责解析Shortcode标签的属性。比如,对于[my_button color="red" size="large"]
这个标签,shortcode_parse_atts()
函数会把属性解析成一个数组:array('color' => 'red', 'size' => 'large')
。
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( '/[x00-x20x7f-xff]+/', ' ', $text );
$text = trim( $text );
if ( empty( $text ) ) {
return $atts;
}
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] );
}
}
// Clean up any keys that are integers because of the regex matching any attributes that do not have a key.
foreach ( $atts as $key => $value ) {
if ( is_numeric( $key ) ) {
unset( $atts[ $key ] );
$atts[] = $value;
}
}
if ( ! empty( $atts ) ) {
return $atts;
}
return false;
}
这个函数也使用了正则表达式来解析属性,支持单引号、双引号和无引号的属性值。
call_user_func()
: 这是PHP的一个内置函数,可以动态地调用一个函数。在这里,call_user_func()
函数会调用我们用add_shortcode()
函数注册的处理函数,并把属性数组、Shortcode内容和标签名作为参数传递给它。
Shortcode的参数
Shortcode处理函数通常接受三个参数:
$atts
: 一个数组,包含Shortcode标签的属性。$content
: Shortcode标签的内容,也就是位于开始标签和结束标签之间的内容。如果Shortcode是自闭合的,那么$content
为null
。$tag
: Shortcode标签的名字。
带属性和内容的Shortcode
除了简单的Shortcode,我们还可以创建带属性和内容的Shortcode。
比如,我们可以创建一个Shortcode,用来显示一个带颜色的文本框:
function my_box_shortcode( $atts, $content = null ) {
$atts = shortcode_atts( array(
'color' => 'gray',
), $atts );
$color = esc_attr( $atts['color'] );
$content = do_shortcode( $content ); // 允许嵌套 Shortcode
return '<div style="background-color: ' . $color . '; padding: 10px;">' . $content . '</div>';
}
add_shortcode( 'my_box', 'my_box_shortcode' );
这里,我们用shortcode_atts()
函数来设置属性的默认值。然后,我们用esc_attr()
函数来转义属性值,防止XSS攻击。最后,我们把Shortcode的内容用do_shortcode()
函数处理一遍,这样就可以在Shortcode的内容里嵌套其他的Shortcode了。
现在,你就可以在文章或者页面里输入[my_box color="red"]这是一段红色的文本[/my_box]
,WordPress就会把它替换成一个红色的文本框。
总结
add_shortcode()
和do_shortcode()
函数是WordPress Shortcode API的核心。add_shortcode()
函数负责注册Shortcode标签和对应的处理函数,do_shortcode()
函数负责扫描文章内容,找到所有的Shortcode标签,然后调用对应的处理函数,把标签替换成最终的内容。
理解了这两个函数的源码,你就可以轻松地创建自己的Shortcode,实现各种各样的功能,让你的WordPress网站更加强大和灵活。
一些建议
- 使用
shortcode_atts()
函数来设置属性的默认值。 - 使用
esc_attr()
函数来转义属性值,防止XSS攻击。 - 在Shortcode的处理函数里,把Shortcode的内容用
do_shortcode()
函数处理一遍,这样就可以在Shortcode的内容里嵌套其他的Shortcode了。 - 注意性能问题。如果你的Shortcode处理函数比较复杂,或者你的文章内容里有很多Shortcode标签,可能会影响网站的性能。
希望今天的讲解对大家有所帮助!下次再见!