0
|
1 |
<?php |
|
2 |
/** |
|
3 |
* WordPress API for creating bbcode like tags or what WordPress calls |
|
4 |
* "shortcodes." The tag and attribute parsing or regular expression code is |
|
5 |
* based on the Textpattern tag parser. |
|
6 |
* |
|
7 |
* A few examples are below: |
|
8 |
* |
|
9 |
* [shortcode /] |
|
10 |
* [shortcode foo="bar" baz="bing" /] |
|
11 |
* [shortcode foo="bar"]content[/shortcode] |
|
12 |
* |
|
13 |
* Shortcode tags support attributes and enclosed content, but does not entirely |
|
14 |
* support inline shortcodes in other shortcodes. You will have to call the |
|
15 |
* shortcode parser in your function to account for that. |
|
16 |
* |
|
17 |
* {@internal |
|
18 |
* Please be aware that the above note was made during the beta of WordPress 2.6 |
|
19 |
* and in the future may not be accurate. Please update the note when it is no |
|
20 |
* longer the case.}} |
|
21 |
* |
|
22 |
* To apply shortcode tags to content: |
|
23 |
* |
|
24 |
* <code> |
|
25 |
* $out = do_shortcode($content); |
|
26 |
* </code> |
|
27 |
* |
|
28 |
* @link http://codex.wordpress.org/Shortcode_API |
|
29 |
* |
|
30 |
* @package WordPress |
|
31 |
* @subpackage Shortcodes |
|
32 |
* @since 2.5 |
|
33 |
*/ |
|
34 |
|
|
35 |
/** |
|
36 |
* Container for storing shortcode tags and their hook to call for the shortcode |
|
37 |
* |
|
38 |
* @since 2.5 |
|
39 |
* @name $shortcode_tags |
|
40 |
* @var array |
|
41 |
* @global array $shortcode_tags |
|
42 |
*/ |
|
43 |
$shortcode_tags = array(); |
|
44 |
|
|
45 |
/** |
|
46 |
* Add hook for shortcode tag. |
|
47 |
* |
|
48 |
* There can only be one hook for each shortcode. Which means that if another |
|
49 |
* plugin has a similar shortcode, it will override yours or yours will override |
|
50 |
* theirs depending on which order the plugins are included and/or ran. |
|
51 |
* |
|
52 |
* Simplest example of a shortcode tag using the API: |
|
53 |
* |
|
54 |
* <code> |
|
55 |
* // [footag foo="bar"] |
|
56 |
* function footag_func($atts) { |
|
57 |
* return "foo = {$atts[foo]}"; |
|
58 |
* } |
|
59 |
* add_shortcode('footag', 'footag_func'); |
|
60 |
* </code> |
|
61 |
* |
|
62 |
* Example with nice attribute defaults: |
|
63 |
* |
|
64 |
* <code> |
|
65 |
* // [bartag foo="bar"] |
|
66 |
* function bartag_func($atts) { |
|
67 |
* extract(shortcode_atts(array( |
|
68 |
* 'foo' => 'no foo', |
|
69 |
* 'baz' => 'default baz', |
|
70 |
* ), $atts)); |
|
71 |
* |
|
72 |
* return "foo = {$foo}"; |
|
73 |
* } |
|
74 |
* add_shortcode('bartag', 'bartag_func'); |
|
75 |
* </code> |
|
76 |
* |
|
77 |
* Example with enclosed content: |
|
78 |
* |
|
79 |
* <code> |
|
80 |
* // [baztag]content[/baztag] |
|
81 |
* function baztag_func($atts, $content='') { |
|
82 |
* return "content = $content"; |
|
83 |
* } |
|
84 |
* add_shortcode('baztag', 'baztag_func'); |
|
85 |
* </code> |
|
86 |
* |
|
87 |
* @since 2.5 |
|
88 |
* @uses $shortcode_tags |
|
89 |
* |
|
90 |
* @param string $tag Shortcode tag to be searched in post content. |
|
91 |
* @param callable $func Hook to run when shortcode is found. |
|
92 |
*/ |
|
93 |
function add_shortcode($tag, $func) { |
|
94 |
global $shortcode_tags; |
|
95 |
|
|
96 |
if ( is_callable($func) ) |
|
97 |
$shortcode_tags[$tag] = $func; |
|
98 |
} |
|
99 |
|
|
100 |
/** |
|
101 |
* Removes hook for shortcode. |
|
102 |
* |
|
103 |
* @since 2.5 |
|
104 |
* @uses $shortcode_tags |
|
105 |
* |
|
106 |
* @param string $tag shortcode tag to remove hook for. |
|
107 |
*/ |
|
108 |
function remove_shortcode($tag) { |
|
109 |
global $shortcode_tags; |
|
110 |
|
|
111 |
unset($shortcode_tags[$tag]); |
|
112 |
} |
|
113 |
|
|
114 |
/** |
|
115 |
* Clear all shortcodes. |
|
116 |
* |
|
117 |
* This function is simple, it clears all of the shortcode tags by replacing the |
|
118 |
* shortcodes global by a empty array. This is actually a very efficient method |
|
119 |
* for removing all shortcodes. |
|
120 |
* |
|
121 |
* @since 2.5 |
|
122 |
* @uses $shortcode_tags |
|
123 |
*/ |
|
124 |
function remove_all_shortcodes() { |
|
125 |
global $shortcode_tags; |
|
126 |
|
|
127 |
$shortcode_tags = array(); |
|
128 |
} |
|
129 |
|
|
130 |
/** |
|
131 |
* Whether a registered shortcode exists named $tag |
|
132 |
* |
|
133 |
* @since 3.6.0 |
|
134 |
* |
|
135 |
* @global array $shortcode_tags |
|
136 |
* @param string $tag |
|
137 |
* @return boolean |
|
138 |
*/ |
|
139 |
function shortcode_exists( $tag ) { |
|
140 |
global $shortcode_tags; |
|
141 |
return array_key_exists( $tag, $shortcode_tags ); |
|
142 |
} |
|
143 |
|
|
144 |
/** |
|
145 |
* Whether the passed content contains the specified shortcode |
|
146 |
* |
|
147 |
* @since 3.6.0 |
|
148 |
* |
|
149 |
* @global array $shortcode_tags |
|
150 |
* @param string $tag |
|
151 |
* @return boolean |
|
152 |
*/ |
|
153 |
function has_shortcode( $content, $tag ) { |
|
154 |
if ( shortcode_exists( $tag ) ) { |
|
155 |
preg_match_all( '/' . get_shortcode_regex() . '/s', $content, $matches, PREG_SET_ORDER ); |
|
156 |
if ( empty( $matches ) ) |
|
157 |
return false; |
|
158 |
|
|
159 |
foreach ( $matches as $shortcode ) { |
|
160 |
if ( $tag === $shortcode[2] ) |
|
161 |
return true; |
|
162 |
} |
|
163 |
} |
|
164 |
return false; |
|
165 |
} |
|
166 |
|
|
167 |
/** |
|
168 |
* Search content for shortcodes and filter shortcodes through their hooks. |
|
169 |
* |
|
170 |
* If there are no shortcode tags defined, then the content will be returned |
|
171 |
* without any filtering. This might cause issues when plugins are disabled but |
|
172 |
* the shortcode will still show up in the post or content. |
|
173 |
* |
|
174 |
* @since 2.5 |
|
175 |
* @uses $shortcode_tags |
|
176 |
* @uses get_shortcode_regex() Gets the search pattern for searching shortcodes. |
|
177 |
* |
|
178 |
* @param string $content Content to search for shortcodes |
|
179 |
* @return string Content with shortcodes filtered out. |
|
180 |
*/ |
|
181 |
function do_shortcode($content) { |
|
182 |
global $shortcode_tags; |
|
183 |
|
|
184 |
if (empty($shortcode_tags) || !is_array($shortcode_tags)) |
|
185 |
return $content; |
|
186 |
|
|
187 |
$pattern = get_shortcode_regex(); |
|
188 |
return preg_replace_callback( "/$pattern/s", 'do_shortcode_tag', $content ); |
|
189 |
} |
|
190 |
|
|
191 |
/** |
|
192 |
* Retrieve the shortcode regular expression for searching. |
|
193 |
* |
|
194 |
* The regular expression combines the shortcode tags in the regular expression |
|
195 |
* in a regex class. |
|
196 |
* |
|
197 |
* The regular expression contains 6 different sub matches to help with parsing. |
|
198 |
* |
|
199 |
* 1 - An extra [ to allow for escaping shortcodes with double [[]] |
|
200 |
* 2 - The shortcode name |
|
201 |
* 3 - The shortcode argument list |
|
202 |
* 4 - The self closing / |
|
203 |
* 5 - The content of a shortcode when it wraps some content. |
|
204 |
* 6 - An extra ] to allow for escaping shortcodes with double [[]] |
|
205 |
* |
|
206 |
* @since 2.5 |
|
207 |
* @uses $shortcode_tags |
|
208 |
* |
|
209 |
* @return string The shortcode search regular expression |
|
210 |
*/ |
|
211 |
function get_shortcode_regex() { |
|
212 |
global $shortcode_tags; |
|
213 |
$tagnames = array_keys($shortcode_tags); |
|
214 |
$tagregexp = join( '|', array_map('preg_quote', $tagnames) ); |
|
215 |
|
|
216 |
// WARNING! Do not change this regex without changing do_shortcode_tag() and strip_shortcode_tag() |
|
217 |
// Also, see shortcode_unautop() and shortcode.js. |
|
218 |
return |
|
219 |
'\\[' // Opening bracket |
|
220 |
. '(\\[?)' // 1: Optional second opening bracket for escaping shortcodes: [[tag]] |
|
221 |
. "($tagregexp)" // 2: Shortcode name |
|
222 |
. '(?![\\w-])' // Not followed by word character or hyphen |
|
223 |
. '(' // 3: Unroll the loop: Inside the opening shortcode tag |
|
224 |
. '[^\\]\\/]*' // Not a closing bracket or forward slash |
|
225 |
. '(?:' |
|
226 |
. '\\/(?!\\])' // A forward slash not followed by a closing bracket |
|
227 |
. '[^\\]\\/]*' // Not a closing bracket or forward slash |
|
228 |
. ')*?' |
|
229 |
. ')' |
|
230 |
. '(?:' |
|
231 |
. '(\\/)' // 4: Self closing tag ... |
|
232 |
. '\\]' // ... and closing bracket |
|
233 |
. '|' |
|
234 |
. '\\]' // Closing bracket |
|
235 |
. '(?:' |
|
236 |
. '(' // 5: Unroll the loop: Optionally, anything between the opening and closing shortcode tags |
|
237 |
. '[^\\[]*+' // Not an opening bracket |
|
238 |
. '(?:' |
|
239 |
. '\\[(?!\\/\\2\\])' // An opening bracket not followed by the closing shortcode tag |
|
240 |
. '[^\\[]*+' // Not an opening bracket |
|
241 |
. ')*+' |
|
242 |
. ')' |
|
243 |
. '\\[\\/\\2\\]' // Closing shortcode tag |
|
244 |
. ')?' |
|
245 |
. ')' |
|
246 |
. '(\\]?)'; // 6: Optional second closing brocket for escaping shortcodes: [[tag]] |
|
247 |
} |
|
248 |
|
|
249 |
/** |
|
250 |
* Regular Expression callable for do_shortcode() for calling shortcode hook. |
|
251 |
* @see get_shortcode_regex for details of the match array contents. |
|
252 |
* |
|
253 |
* @since 2.5 |
|
254 |
* @access private |
|
255 |
* @uses $shortcode_tags |
|
256 |
* |
|
257 |
* @param array $m Regular expression match array |
|
258 |
* @return mixed False on failure. |
|
259 |
*/ |
|
260 |
function do_shortcode_tag( $m ) { |
|
261 |
global $shortcode_tags; |
|
262 |
|
|
263 |
// allow [[foo]] syntax for escaping a tag |
|
264 |
if ( $m[1] == '[' && $m[6] == ']' ) { |
|
265 |
return substr($m[0], 1, -1); |
|
266 |
} |
|
267 |
|
|
268 |
$tag = $m[2]; |
|
269 |
$attr = shortcode_parse_atts( $m[3] ); |
|
270 |
|
|
271 |
if ( isset( $m[5] ) ) { |
|
272 |
// enclosing tag - extra parameter |
|
273 |
return $m[1] . call_user_func( $shortcode_tags[$tag], $attr, $m[5], $tag ) . $m[6]; |
|
274 |
} else { |
|
275 |
// self-closing tag |
|
276 |
return $m[1] . call_user_func( $shortcode_tags[$tag], $attr, null, $tag ) . $m[6]; |
|
277 |
} |
|
278 |
} |
|
279 |
|
|
280 |
/** |
|
281 |
* Retrieve all attributes from the shortcodes tag. |
|
282 |
* |
|
283 |
* The attributes list has the attribute name as the key and the value of the |
|
284 |
* attribute as the value in the key/value pair. This allows for easier |
|
285 |
* retrieval of the attributes, since all attributes have to be known. |
|
286 |
* |
|
287 |
* @since 2.5 |
|
288 |
* |
|
289 |
* @param string $text |
|
290 |
* @return array List of attributes and their value. |
|
291 |
*/ |
|
292 |
function shortcode_parse_atts($text) { |
|
293 |
$atts = array(); |
|
294 |
$pattern = '/(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/'; |
|
295 |
$text = preg_replace("/[\x{00a0}\x{200b}]+/u", " ", $text); |
|
296 |
if ( preg_match_all($pattern, $text, $match, PREG_SET_ORDER) ) { |
|
297 |
foreach ($match as $m) { |
|
298 |
if (!empty($m[1])) |
|
299 |
$atts[strtolower($m[1])] = stripcslashes($m[2]); |
|
300 |
elseif (!empty($m[3])) |
|
301 |
$atts[strtolower($m[3])] = stripcslashes($m[4]); |
|
302 |
elseif (!empty($m[5])) |
|
303 |
$atts[strtolower($m[5])] = stripcslashes($m[6]); |
|
304 |
elseif (isset($m[7]) and strlen($m[7])) |
|
305 |
$atts[] = stripcslashes($m[7]); |
|
306 |
elseif (isset($m[8])) |
|
307 |
$atts[] = stripcslashes($m[8]); |
|
308 |
} |
|
309 |
} else { |
|
310 |
$atts = ltrim($text); |
|
311 |
} |
|
312 |
return $atts; |
|
313 |
} |
|
314 |
|
|
315 |
/** |
|
316 |
* Combine user attributes with known attributes and fill in defaults when needed. |
|
317 |
* |
|
318 |
* The pairs should be considered to be all of the attributes which are |
|
319 |
* supported by the caller and given as a list. The returned attributes will |
|
320 |
* only contain the attributes in the $pairs list. |
|
321 |
* |
|
322 |
* If the $atts list has unsupported attributes, then they will be ignored and |
|
323 |
* removed from the final returned list. |
|
324 |
* |
|
325 |
* @since 2.5 |
|
326 |
* |
|
327 |
* @param array $pairs Entire list of supported attributes and their defaults. |
|
328 |
* @param array $atts User defined attributes in shortcode tag. |
|
329 |
* @param string $shortcode Optional. The name of the shortcode, provided for context to enable filtering |
|
330 |
* @return array Combined and filtered attribute list. |
|
331 |
*/ |
|
332 |
function shortcode_atts( $pairs, $atts, $shortcode = '' ) { |
|
333 |
$atts = (array)$atts; |
|
334 |
$out = array(); |
|
335 |
foreach($pairs as $name => $default) { |
|
336 |
if ( array_key_exists($name, $atts) ) |
|
337 |
$out[$name] = $atts[$name]; |
|
338 |
else |
|
339 |
$out[$name] = $default; |
|
340 |
} |
|
341 |
/** |
|
342 |
* Filter a shortcode's default attributes. |
|
343 |
* |
|
344 |
* If the third parameter of the shortcode_atts() function is present then this filter is available. |
|
345 |
* The third parameter, $shortcode, is the name of the shortcode. |
|
346 |
* |
|
347 |
* @since 3.6.0 |
|
348 |
* |
|
349 |
* @param array $out The output array of shortcode attributes. |
|
350 |
* @param array $pairs The supported attributes and their defaults. |
|
351 |
* @param array $atts The user defined shortcode attributes. |
|
352 |
*/ |
|
353 |
if ( $shortcode ) |
|
354 |
$out = apply_filters( "shortcode_atts_{$shortcode}", $out, $pairs, $atts ); |
|
355 |
|
|
356 |
return $out; |
|
357 |
} |
|
358 |
|
|
359 |
/** |
|
360 |
* Remove all shortcode tags from the given content. |
|
361 |
* |
|
362 |
* @since 2.5 |
|
363 |
* @uses $shortcode_tags |
|
364 |
* |
|
365 |
* @param string $content Content to remove shortcode tags. |
|
366 |
* @return string Content without shortcode tags. |
|
367 |
*/ |
|
368 |
function strip_shortcodes( $content ) { |
|
369 |
global $shortcode_tags; |
|
370 |
|
|
371 |
if (empty($shortcode_tags) || !is_array($shortcode_tags)) |
|
372 |
return $content; |
|
373 |
|
|
374 |
$pattern = get_shortcode_regex(); |
|
375 |
|
|
376 |
return preg_replace_callback( "/$pattern/s", 'strip_shortcode_tag', $content ); |
|
377 |
} |
|
378 |
|
|
379 |
function strip_shortcode_tag( $m ) { |
|
380 |
// allow [[foo]] syntax for escaping a tag |
|
381 |
if ( $m[1] == '[' && $m[6] == ']' ) { |
|
382 |
return substr($m[0], 1, -1); |
|
383 |
} |
|
384 |
|
|
385 |
return $m[1] . $m[6]; |
|
386 |
} |
|
387 |
|
|
388 |
add_filter('the_content', 'do_shortcode', 11); // AFTER wpautop() |