author | Anthony Ly <anthonyly.com@gmail.com> |
Wed, 19 Dec 2012 12:35:13 -0800 | |
changeset 203 | f507feede89a |
parent 194 | 32102edaa81b |
child 204 | 09a1c134465b |
permissions | -rw-r--r-- |
136 | 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 |
* Search content for shortcodes and filter shortcodes through their hooks. |
|
132 |
* |
|
133 |
* If there are no shortcode tags defined, then the content will be returned |
|
134 |
* without any filtering. This might cause issues when plugins are disabled but |
|
135 |
* the shortcode will still show up in the post or content. |
|
136 |
* |
|
137 |
* @since 2.5 |
|
138 |
* @uses $shortcode_tags |
|
139 |
* @uses get_shortcode_regex() Gets the search pattern for searching shortcodes. |
|
140 |
* |
|
141 |
* @param string $content Content to search for shortcodes |
|
142 |
* @return string Content with shortcodes filtered out. |
|
143 |
*/ |
|
144 |
function do_shortcode($content) { |
|
145 |
global $shortcode_tags; |
|
146 |
||
147 |
if (empty($shortcode_tags) || !is_array($shortcode_tags)) |
|
148 |
return $content; |
|
149 |
||
150 |
$pattern = get_shortcode_regex(); |
|
194
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
151 |
return preg_replace_callback( "/$pattern/s", 'do_shortcode_tag', $content ); |
136 | 152 |
} |
153 |
||
154 |
/** |
|
155 |
* Retrieve the shortcode regular expression for searching. |
|
156 |
* |
|
157 |
* The regular expression combines the shortcode tags in the regular expression |
|
158 |
* in a regex class. |
|
159 |
* |
|
194
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
160 |
* The regular expression contains 6 different sub matches to help with parsing. |
136 | 161 |
* |
194
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
162 |
* 1 - An extra [ to allow for escaping shortcodes with double [[]] |
136 | 163 |
* 2 - The shortcode name |
164 |
* 3 - The shortcode argument list |
|
165 |
* 4 - The self closing / |
|
166 |
* 5 - The content of a shortcode when it wraps some content. |
|
194
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
167 |
* 6 - An extra ] to allow for escaping shortcodes with double [[]] |
136 | 168 |
* |
169 |
* @since 2.5 |
|
170 |
* @uses $shortcode_tags |
|
171 |
* |
|
172 |
* @return string The shortcode search regular expression |
|
173 |
*/ |
|
174 |
function get_shortcode_regex() { |
|
175 |
global $shortcode_tags; |
|
176 |
$tagnames = array_keys($shortcode_tags); |
|
177 |
$tagregexp = join( '|', array_map('preg_quote', $tagnames) ); |
|
178 |
||
194
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
179 |
// WARNING! Do not change this regex without changing do_shortcode_tag() and strip_shortcode_tag() |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
180 |
return |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
181 |
'\\[' // Opening bracket |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
182 |
. '(\\[?)' // 1: Optional second opening bracket for escaping shortcodes: [[tag]] |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
183 |
. "($tagregexp)" // 2: Shortcode name |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
184 |
. '\\b' // Word boundary |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
185 |
. '(' // 3: Unroll the loop: Inside the opening shortcode tag |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
186 |
. '[^\\]\\/]*' // Not a closing bracket or forward slash |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
187 |
. '(?:' |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
188 |
. '\\/(?!\\])' // A forward slash not followed by a closing bracket |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
189 |
. '[^\\]\\/]*' // Not a closing bracket or forward slash |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
190 |
. ')*?' |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
191 |
. ')' |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
192 |
. '(?:' |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
193 |
. '(\\/)' // 4: Self closing tag ... |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
194 |
. '\\]' // ... and closing bracket |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
195 |
. '|' |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
196 |
. '\\]' // Closing bracket |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
197 |
. '(?:' |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
198 |
. '(' // 5: Unroll the loop: Optionally, anything between the opening and closing shortcode tags |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
199 |
. '[^\\[]*+' // Not an opening bracket |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
200 |
. '(?:' |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
201 |
. '\\[(?!\\/\\2\\])' // An opening bracket not followed by the closing shortcode tag |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
202 |
. '[^\\[]*+' // Not an opening bracket |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
203 |
. ')*+' |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
204 |
. ')' |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
205 |
. '\\[\\/\\2\\]' // Closing shortcode tag |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
206 |
. ')?' |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
207 |
. ')' |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
208 |
. '(\\]?)'; // 6: Optional second closing brocket for escaping shortcodes: [[tag]] |
136 | 209 |
} |
210 |
||
211 |
/** |
|
212 |
* Regular Expression callable for do_shortcode() for calling shortcode hook. |
|
213 |
* @see get_shortcode_regex for details of the match array contents. |
|
214 |
* |
|
215 |
* @since 2.5 |
|
216 |
* @access private |
|
217 |
* @uses $shortcode_tags |
|
218 |
* |
|
219 |
* @param array $m Regular expression match array |
|
220 |
* @return mixed False on failure. |
|
221 |
*/ |
|
194
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
222 |
function do_shortcode_tag( $m ) { |
136 | 223 |
global $shortcode_tags; |
224 |
||
225 |
// allow [[foo]] syntax for escaping a tag |
|
194
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
226 |
if ( $m[1] == '[' && $m[6] == ']' ) { |
136 | 227 |
return substr($m[0], 1, -1); |
228 |
} |
|
229 |
||
230 |
$tag = $m[2]; |
|
194
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
231 |
$attr = shortcode_parse_atts( $m[3] ); |
136 | 232 |
|
194
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
233 |
if ( isset( $m[5] ) ) { |
136 | 234 |
// enclosing tag - extra parameter |
194
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
235 |
return $m[1] . call_user_func( $shortcode_tags[$tag], $attr, $m[5], $tag ) . $m[6]; |
136 | 236 |
} else { |
237 |
// self-closing tag |
|
194
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
238 |
return $m[1] . call_user_func( $shortcode_tags[$tag], $attr, null, $tag ) . $m[6]; |
136 | 239 |
} |
240 |
} |
|
241 |
||
242 |
/** |
|
243 |
* Retrieve all attributes from the shortcodes tag. |
|
244 |
* |
|
245 |
* The attributes list has the attribute name as the key and the value of the |
|
246 |
* attribute as the value in the key/value pair. This allows for easier |
|
247 |
* retrieval of the attributes, since all attributes have to be known. |
|
248 |
* |
|
249 |
* @since 2.5 |
|
250 |
* |
|
251 |
* @param string $text |
|
252 |
* @return array List of attributes and their value. |
|
253 |
*/ |
|
254 |
function shortcode_parse_atts($text) { |
|
255 |
$atts = array(); |
|
256 |
$pattern = '/(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/'; |
|
257 |
$text = preg_replace("/[\x{00a0}\x{200b}]+/u", " ", $text); |
|
258 |
if ( preg_match_all($pattern, $text, $match, PREG_SET_ORDER) ) { |
|
259 |
foreach ($match as $m) { |
|
260 |
if (!empty($m[1])) |
|
261 |
$atts[strtolower($m[1])] = stripcslashes($m[2]); |
|
262 |
elseif (!empty($m[3])) |
|
263 |
$atts[strtolower($m[3])] = stripcslashes($m[4]); |
|
264 |
elseif (!empty($m[5])) |
|
265 |
$atts[strtolower($m[5])] = stripcslashes($m[6]); |
|
266 |
elseif (isset($m[7]) and strlen($m[7])) |
|
267 |
$atts[] = stripcslashes($m[7]); |
|
268 |
elseif (isset($m[8])) |
|
269 |
$atts[] = stripcslashes($m[8]); |
|
270 |
} |
|
271 |
} else { |
|
272 |
$atts = ltrim($text); |
|
273 |
} |
|
274 |
return $atts; |
|
275 |
} |
|
276 |
||
277 |
/** |
|
278 |
* Combine user attributes with known attributes and fill in defaults when needed. |
|
279 |
* |
|
280 |
* The pairs should be considered to be all of the attributes which are |
|
281 |
* supported by the caller and given as a list. The returned attributes will |
|
282 |
* only contain the attributes in the $pairs list. |
|
283 |
* |
|
284 |
* If the $atts list has unsupported attributes, then they will be ignored and |
|
285 |
* removed from the final returned list. |
|
286 |
* |
|
287 |
* @since 2.5 |
|
288 |
* |
|
289 |
* @param array $pairs Entire list of supported attributes and their defaults. |
|
290 |
* @param array $atts User defined attributes in shortcode tag. |
|
291 |
* @return array Combined and filtered attribute list. |
|
292 |
*/ |
|
293 |
function shortcode_atts($pairs, $atts) { |
|
294 |
$atts = (array)$atts; |
|
295 |
$out = array(); |
|
296 |
foreach($pairs as $name => $default) { |
|
297 |
if ( array_key_exists($name, $atts) ) |
|
298 |
$out[$name] = $atts[$name]; |
|
299 |
else |
|
300 |
$out[$name] = $default; |
|
301 |
} |
|
302 |
return $out; |
|
303 |
} |
|
304 |
||
305 |
/** |
|
306 |
* Remove all shortcode tags from the given content. |
|
307 |
* |
|
308 |
* @since 2.5 |
|
309 |
* @uses $shortcode_tags |
|
310 |
* |
|
311 |
* @param string $content Content to remove shortcode tags. |
|
312 |
* @return string Content without shortcode tags. |
|
313 |
*/ |
|
314 |
function strip_shortcodes( $content ) { |
|
315 |
global $shortcode_tags; |
|
316 |
||
317 |
if (empty($shortcode_tags) || !is_array($shortcode_tags)) |
|
318 |
return $content; |
|
319 |
||
320 |
$pattern = get_shortcode_regex(); |
|
321 |
||
194
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
322 |
return preg_replace_callback( "/$pattern/s", 'strip_shortcode_tag', $content ); |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
323 |
} |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
324 |
|
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
325 |
function strip_shortcode_tag( $m ) { |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
326 |
// allow [[foo]] syntax for escaping a tag |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
327 |
if ( $m[1] == '[' && $m[6] == ']' ) { |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
328 |
return substr($m[0], 1, -1); |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
329 |
} |
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
330 |
|
32102edaa81b
MAJ wordpress et ajout de plugin
Anthony Ly <anthonyly.com@gmail.com>
parents:
136
diff
changeset
|
331 |
return $m[1] . $m[6]; |
136 | 332 |
} |
333 |
||
334 |
add_filter('the_content', 'do_shortcode', 11); // AFTER wpautop() |