Ticket #14481: shortcode.patch
File shortcode.patch, 28.4 KB (added by , 14 years ago) |
---|
-
shortcodes.php
1 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 */ 2 /* 3 Author: Jacob Beauregard (jrbeaure at uvm dot edu) 4 */ 34 5 6 /* 7 8 From Test Cases (http://svn.automattic.com/wordpress-tests/wp-testcase/test_shortcode.php) 9 ========================================================================================== 10 Shortcode Statuses (to implement, or not to implement?) 11 enabled = the shortcode works as normal (default) 12 strip = the shortcode will be parsed and removed. e.g. '[shortcode foo="bar"]' produces ''. '[shortcode]foo[/shortcode]' produces 'foo'. 13 faux = the shortcode will be abbreviated. e.g. '[shortcode foo="bar"]' products '[shortcode]'. '[shortocde]foo[/shortcode]' produces '[shortcode]' 14 disabled = the shortcode is not parsed at all. e.g. '[shortcode foo="bar"]' products '[shortcode foo="bar"]' 15 16 17 Major Differences/Improvements: 18 =============================== 19 I. Addressing http://codex.wordpress.org/Shortcode_API#Limitations 20 21 1. You can nest any tag at any depth regardless of ancestors (Ticket #10702) 22 23 2. Tag and attribute names may match /[A-Za-z][-A-Za-z0-9_:.]*//* (trialing /* because that comment ends), 24 with case-insensitive interpretation 25 26 3. Interpretation doesn't get tripped up by things like hyphens 27 28 II. Addressing Ticket #12760, 29 30 1. Changed from fix in #6518 31 Reasoning: balancing double-square-brackets can have misleading results with nesting 32 33 2. Shortcodes escapable by using [[, ]] 34 35 3. ']]' is escaped "right to left", so '[shortcode]]]' would evaluate to 'result]' 36 37 4. '[[' is escaped left to right '[[[shortcode]]]' => '[result]' 38 39 III. Enhancements 40 41 1. Only matches valid shortcode for registered hooks, everything else will appear as text 42 43 2. Can register multiple hooks to single shortcode, uses priority (default: 10) 44 45 IV. Conflicting Design Changes 46 47 1. Quoted literals are escaped by entities rather than cslashes 48 49 2. Inline (self-closing) shortcodes get passed content to accomodate multiple callbacks 50 51 3. No equivalent to shortcode_parse_atts function 52 (Not marked private in function reference, but not documented in shortcode API page) 53 54 4. Boolean attributes take the place of positional attributes 55 [foo bar] gets attributes array('bar' => 'bar') instead of array('0' => 'bar') 56 57 5. Disallows attribute and tag names that don't match /[A-Za-z][-A-Za-z0-9_:.]*/ 58 59 //pardon me /* 60 61 6. Disallows unquoted attribute values, unless they match /[-A-Za-z0-9_:.]+/ 62 63 64 VI. Addressing Tickets #9264, #10082 (see following, `Basic Interpretation Method`) 65 66 67 Basic Interpretation Method: 68 ============================ 69 1. If an open tag is encountered, it is added to the stack 70 71 2. If a close tag is encountered and there is no matching open tag on the stack 72 the close tag is ignored 73 74 3. If a close tag is encountered and there is a matching open tag on the stack 75 all opened tags on the stack before the matched tag will be implicitly self-closed 76 77 4. If text or an inline tag is encountered, it will be evaluated to its parent's 78 content immediately 79 80 5. If tags are not closed by the end of the interpretation, they will be implicitly 81 self-closed 82 83 84 Issues: 85 ======= 86 1. Haven't written new unit tests to reflect new functionality added 87 88 2. Documentation is not as good (though I hope most of the code is self-explanatory) 89 90 3. Not 100% backwards compatible 91 92 */ 93 94 /* +++++ PUBLIC FUNCTIONS ++++++ */ 95 35 96 /** 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(); 97 * add hook for shortcode tag 98 */ 99 function add_shortcode($name,$handler,$priority=10) { 100 $name = strtolower($name); 101 shortcode_register_set('priority',$name,$handler,$priority); 102 shortcode_register_set('callback',$name,$priority,$handler,true); 103 } 44 104 45 105 /** 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; 106 * remove hook for shortcode tag 107 */ 108 function remove_shortcode($name,$handler) { 109 $name = strtolower($name); 110 $priority = shortcode_register_get('priority',$name,$handler); 95 111 96 if ( is_callable($func) )97 $shortcode_tags[$tag] = $func;112 shortcode_register_unset('priority',$name,$handler); 113 shortcode_register_unset('callback',$name,$priority,$handler); 98 114 } 99 115 100 116 /** 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; 117 * check if handler is hooked to shortcode tag, returns priority 118 */ 119 function has_shortcode($name,$handler) { 120 $name = strtolower($name); 121 return shortcode_register_get('priority',$name,$handler); 122 } 110 123 111 unset($shortcode_tags[$tag]); 124 /** 125 * removes all shortcode hooks 126 */ 127 function remove_all_shortcodes($name) { 128 $name = strtolower($name); 129 shortcode_register_unset('priority',$name); 130 shortcode_register_unset('callback',$name); 112 131 } 113 132 114 133 /** 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; 134 * retrieves all shortcode hooks associated with a tag 135 * returns order FCFS by priority 136 */ 137 function get_shortcodes($name) { 138 $name = strtolower($name); 139 $priority_groups = shortcode_register_get('callback',$name); 140 $callbacks = array(); 141 if (is_array($priority_groups)) { 142 foreach ($priority_groups as $priority_group) { 143 $callbacks = array_merge($callbacks,$priority_group); 144 } 145 } 146 return array_keys($callbacks); 147 } 126 148 127 $shortcode_tags = array(); 149 /** 150 * executes the shortcode hooks for the shortcode tag with specified attributes 151 * initial content is $content, but is actively modified by each handler 152 */ 153 function apply_shortcodes($attrs,$content,$name) { 154 $name = strtolower($name); 155 $callbacks = get_shortcodes($name); 156 157 if (is_array($callbacks)) { 158 foreach ($callbacks as $callback) { 159 if (function_exists($callback)) { 160 $content = $callback($attrs,$content,$name); 161 } 162 } 163 } 164 165 return $content; 128 166 } 129 167 130 168 /** 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; 169 * removes shortcodes with registered handlers from page 170 */ 171 function strip_shortcodes($content) { 172 $sc_re = "/" . shortcode_re_combine(array('tag_inline','tag_open','tag_close')) . "/"; 173 $content = preg_replace($sc_re,'',$content); 174 return $content; 175 } 146 176 147 if (empty($shortcode_tags) || !is_array($shortcode_tags)) 148 return $content; 177 /** 178 * alias of array_merge, unless we want to special case positional attributes 179 */ 180 function shortcode_atts($defaults, $attrs) { 181 return array_merge($defaults, $attrs); 182 } 149 183 150 $pattern = get_shortcode_regex(); 151 return preg_replace_callback('/'.$pattern.'/s', 'do_shortcode_tag', $content); 184 /* run main eval function */ 185 function do_shortcode($expr) { 186 return shortcode_eval($expr); 152 187 } 153 188 189 190 191 /* +++++++++++++ THE UNDERPINNINGS +++++++++++ */ 192 154 193 /** 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 * 160 * The regular expresion contains 6 different sub matches to help with parsing. 161 * 162 * 1/6 - An extra [ or ] to allow for escaping shortcodes with double [[]] 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. 167 * 168 * @since 2.5 169 * @uses $shortcode_tags 170 * 171 * @return string The shortcode search regular expression 172 */ 173 function get_shortcode_regex() { 174 global $shortcode_tags; 175 $tagnames = array_keys($shortcode_tags); 176 $tagregexp = join( '|', array_map('preg_quote', $tagnames) ); 194 * retrieves all shortcode tags that have at least one registered hook 195 */ 196 function shortcode_tags() { 197 $tags = array_keys(shortcode_register_get('callback')); 198 return $tags; 199 } 177 200 178 // WARNING! Do not change this regex without changing do_shortcode_tag() and strip_shortcodes() 179 return '(.?)\[('.$tagregexp.')\b(.*?)(?:(\/))?\](?:(.+?)\[\/\2\])?(.?)'; 201 /** 202 * keeps track of information shared between many shortcode functions 203 */ 204 //I'm at war with global variables, so I used a static variable instead. 205 function shortcode_register($assignment=null) { 206 static $register = array(); 207 if ($assignment !== null) { 208 $register = $assignment; 209 } 210 return $register; 180 211 } 181 212 182 213 /** 183 * Regular Expression callable for do_shortcode() for calling shortcode hook. 184 * @see get_shortcode_regex for details of the match array contents. 185 * 186 * @since 2.5 187 * @access private 188 * @uses $shortcode_tags 189 * 190 * @param array $m Regular expression match array 191 * @return mixed False on failure. 192 */ 193 function do_shortcode_tag( $m ) { 194 global $shortcode_tags; 214 * gets a value from the register based on attributes 215 */ 216 function shortcode_register_get() { 217 $arguments = func_get_args(); 218 $register = shortcode_register(); 195 219 196 // allow [[foo]] syntax for escaping a tag 197 if ( $m[1] == '[' && $m[6] == ']' ) { 198 return substr($m[0], 1, -1); 220 $value = $register; 221 while (count($arguments) && $value != null) { 222 $key = array_shift($arguments); 223 if (isset($value[$key])) { 224 $value = $value[$key]; 225 } else { 226 $value = null; 227 } 199 228 } 229 return $value; 230 } 200 231 201 $tag = $m[2]; 202 $attr = shortcode_parse_atts( $m[3] ); 232 /** 233 * sets a value in the register 234 */ 235 function shortcode_register_set() { 236 $arguments = func_get_args(); 237 $register = shortcode_register(); 238 $value = array_pop($arguments); 203 239 204 if ( isset( $m[5] ) ) { 205 // enclosing tag - extra parameter 206 return $m[1] . call_user_func( $shortcode_tags[$tag], $attr, $m[5], $tag ) . $m[6]; 207 } else { 208 // self-closing tag 209 return $m[1] . call_user_func( $shortcode_tags[$tag], $attr, NULL, $tag ) . $m[6]; 240 $group =& $register; 241 while (count($arguments)) { 242 $key = array_shift($arguments); 243 if (!is_array($group[$key])) { 244 $group[$key] = array(); 245 } 246 $group =& $group[$key]; 210 247 } 248 $group = $value; 249 shortcode_register($register); 211 250 } 212 251 213 252 /** 214 * Retrieve all attributes from the shortcodes tag. 215 * 216 * The attributes list has the attribute name as the key and the value of the 217 * attribute as the value in the key/value pair. This allows for easier 218 * retrieval of the attributes, since all attributes have to be known. 219 * 220 * @since 2.5 221 * 222 * @param string $text 223 * @return array List of attributes and their value. 224 */ 225 function shortcode_parse_atts($text) { 226 $atts = array(); 227 $pattern = '/(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/'; 228 $text = preg_replace("/[\x{00a0}\x{200b}]+/u", " ", $text); 229 if ( preg_match_all($pattern, $text, $match, PREG_SET_ORDER) ) { 230 foreach ($match as $m) { 231 if (!empty($m[1])) 232 $atts[strtolower($m[1])] = stripcslashes($m[2]); 233 elseif (!empty($m[3])) 234 $atts[strtolower($m[3])] = stripcslashes($m[4]); 235 elseif (!empty($m[5])) 236 $atts[strtolower($m[5])] = stripcslashes($m[6]); 237 elseif (isset($m[7]) and strlen($m[7])) 238 $atts[] = stripcslashes($m[7]); 239 elseif (isset($m[8])) 240 $atts[] = stripcslashes($m[8]); 253 * unsets/deletes a value from the register 254 */ 255 function shortcode_register_unset() { 256 $arguments = func_get_args(); 257 $register = short_code_register(); 258 $group =& $register; 259 260 $exists = true; 261 while (count($arguments) > 1 && !$exists) { 262 $key = array_shift($arguments); 263 if (!is_array($group[$key])) { 264 $exists = false; 265 } else { 266 $group =& $group[$key]; 241 267 } 242 } else {243 $atts = ltrim($text);244 268 } 245 return $atts; 269 if ($exists) { 270 $key = array_shift($arguments); 271 unset($group[$key]); 272 } 273 shortcode_register($register); 246 274 } 247 275 276 /* 277 * types of shortcodes to be parsed 278 */ 279 function shortcode_types() { 280 return array( 281 'esc_lsqbr', 282 'esc_rsqbr', 283 'tag_inline', 284 'tag_open', 285 'tag_close', 286 'attr', 287 'literal', 288 'entity_ref', 289 'char_ref', 290 'char_str', 291 'name', 292 'text' 293 ); 294 } 295 296 /* 297 * subtypes of a root (raw) expression 298 */ 299 function shortcode_sub_root() { 300 return array('esc_rsqbr','esc_lsqbr','tag_inline','tag_open','tag_close','text'); 301 } 302 303 /* 304 * subtypes of a tag_inline expression 305 */ 306 function shortcode_sub_tag_inline() { 307 return array('attr','name'); 308 } 309 310 /* 311 * i bet you get the point by now 312 */ 313 function shortcode_sub_tag_open() { 314 return array('attr','name'); 315 } 316 317 function shortcode_sub_tag_close() { 318 return array('name'); 319 } 320 321 function shortcode_sub_text() { 322 return null; 323 } 324 325 function shortcode_sub_name() { 326 return null; 327 } 328 329 function shortcode_sub_attr() { 330 return array('literal','name'); 331 } 332 333 function shortcode_sub_literal() { 334 return array('char_ref','entity_ref','char_str'); 335 } 336 337 function shortcode_sub_char_str() { 338 return null; 339 } 340 341 function shortcode_sub_char_ref() { 342 return null; 343 } 344 345 function shortcode_sub_entity_ref() { 346 //formally it's a name, but it's easier to interpret it with the & and ; 347 return null; //array('name'); 348 } 349 350 function shortcode_sub_esc_lsqbr() { 351 return null; 352 } 353 354 function shortcode_sub_esc_rsqbr() { 355 return null; 356 } 357 248 358 /** 249 * Combine user attributes with known attributes and fill in defaults when needed. 250 * 251 * The pairs should be considered to be all of the attributes which are 252 * supported by the caller and given as a list. The returned attributes will 253 * only contain the attributes in the $pairs list. 254 * 255 * If the $atts list has unsupported attributes, then they will be ignored and 256 * removed from the final returned list. 257 * 258 * @since 2.5 259 * 260 * @param array $pairs Entire list of supported attributes and their defaults. 261 * @param array $atts User defined attributes in shortcode tag. 262 * @return array Combined and filtered attribute list. 263 */ 264 function shortcode_atts($pairs, $atts) { 265 $atts = (array)$atts; 266 $out = array(); 267 foreach($pairs as $name => $default) { 268 if ( array_key_exists($name, $atts) ) 269 $out[$name] = $atts[$name]; 270 else 271 $out[$name] = $default; 359 * returns subtypes of type specified by $type 360 */ 361 function shortcode_sub($type='root') { 362 if (in_array($type,shortcode_types()) || $type == 'root') { 363 $func = "shortcode_sub_{$type}"; 364 $subtypes = $func(); 272 365 } 273 return $ out;366 return $subtypes; 274 367 } 275 368 369 370 // started writing backwards compatibility function 371 // but would really prefer not to support these 372 373 function shortcode_re_compat_name($named=false) { 374 $expr = '\w+'; 375 if ($named) { 376 $expr = "(?P<compat_name>{$expr})"; 377 } 378 return $expr; 379 } 380 381 function shortcode_re_compat_literal($named=false) { 382 $nq = shortcode_re_compat_literal_nq(); 383 $sq = shortcode_re_compat_literal_sq(); 384 $dq = shortcode_re_compat_literal_dq(); 385 $expr = "(?:{$nq}|{$sq}|{$dq})"; 386 if ($named) { 387 $expr = "(?P<compat_literal>{$expr})"; 388 } 389 return $expr; 390 } 391 392 function shortcode_re_compat_literal_dq() { 393 $expr = '"[^"]*"'; 394 return $expr; 395 } 396 397 function shortcode_re_compat_literal_sq() { 398 $expr = "'[^']*'"; 399 return $expr; 400 } 401 402 function shortcode_re_compat_literal_nq() { 403 $expr = '\S+?(?=\])'; 404 return $expr; 405 } 406 407 function shortcode_re_compat_attr($named=false) { 408 $name = shortcode_re_compat_name(); 409 $literal = shortcode_re_compat_literal(); 410 $expr = "(?:{$name}\\s*=)?\\s*{$literal}"; 411 if ($named) { 412 $expr = "(?P<compat_attr>{$expr})"; 413 } 414 return $expr; 415 } 416 276 417 /** 277 * Remove all shortcode tags from the given content. 278 * 279 * @since 2.5 280 * @uses $shortcode_tags 281 * 282 * @param string $content Content to remove shortcode tags. 283 * @return string Content without shortcode tags. 284 */ 285 function strip_shortcodes( $content ) { 286 global $shortcode_tags; 418 * regular expression to match a valid name 419 */ 420 function shortcode_re_name($named=false) { 421 $expr = '[A-Za-z][-A-Za-z0-9_:.]*'; 422 if ($named) { 423 $expr = "(?P<name>{$expr})"; 424 } 425 return $expr; 426 } 287 427 288 if (empty($shortcode_tags) || !is_array($shortcode_tags)) 289 return $content; 428 /** 429 * regular expression to match a name that's registered as a shortcode 430 */ 431 function shortcode_re_registered_name($named=false) { 432 $tags = array_map('preg_quote',shortcode_tags()); 433 $expr = join('|',$tags); 434 $expr = "(?i:{$expr})"; 435 if ($named) { 436 $expr = "(?P<name>{$expr})"; 437 } 438 return $expr; 439 } 290 440 291 $pattern = get_shortcode_regex(); 441 /** 442 * you probably get the point by now 443 */ 444 function shortcode_re_char_str($named=false) { 445 $expr = "[^%&]+"; 446 if ($named) { 447 $expr = "(?P<char_str>{$expr})"; 448 } 449 return $expr; 450 } 292 451 293 return preg_replace('/'.$pattern.'/s', '$1$6', $content); 452 /** 453 * single quote version for shortcode_re_char_str 454 */ 455 function shortcode_re_char_str_sq($named=false) { 456 $expr = "[^%&']+"; 457 if ($named) { 458 $expr = "(?P<char_str>{$expr})"; 459 } 460 return $expr; 294 461 } 295 462 296 add_filter('the_content', 'do_shortcode', 11); // AFTER wpautop() 463 /** 464 * double quote version for shortcode_re_char_str 465 */ 466 function shortcode_re_char_str_dq($named=false) { 467 $expr = '[^%&"]+'; 468 if ($named) { 469 $expr = "(?P<char_str>{$expr})"; 470 } 471 return $expr; 472 } 297 473 298 ?> 299 No newline at end of file 474 function shortcode_re_char_ref($named=false) { 475 $expr = "(?:[0-9]+|x[0-9a-fA-F]+)"; 476 if ($named) { 477 $expr = "(?P<char_ref>{$expr})"; 478 } 479 $expr = "&#{$expr};"; 480 return $expr; 481 } 482 483 function shortcode_re_entity_ref($named=false) { 484 $name = shortcode_re_name(); 485 486 $expr = "&{$name};"; 487 if ($named) { 488 $expr = "(?P<entity_ref>{$expr})"; 489 } 490 return $expr; 491 } 492 493 function shortcode_re_literal($named=false) { 494 $nq = shortcode_re_literal_nq(); 495 $sq = shortcode_re_literal_sq(); 496 $dq = shortcode_re_literal_dq(); 497 $expr = "(?:{$nq}|{$sq}|{$dq})"; 498 if ($named) { 499 $expr = "(?P<literal>{$expr})"; 500 } 501 return $expr; 502 } 503 504 function shortcode_re_literal_nq($named=false) { 505 $expr = "[-a-zA-Z0-9_:.]+"; 506 return $expr; 507 } 508 509 function shortcode_re_literal_sq($named=false) { 510 $char_str = shortcode_re_char_str_sq(); 511 $char_ref = shortcode_re_char_ref(); 512 $entity_ref = shortcode_re_entity_ref(); 513 514 $expr = "'(?:{$char_str}|{$char_ref}|{$entity_ref})*'"; 515 return $expr; 516 } 517 518 function shortcode_re_literal_dq($named=false) { 519 $char_str = shortcode_re_char_str_dq(); 520 $char_ref = shortcode_re_char_ref(); 521 $entity_ref = shortcode_re_entity_ref(); 522 523 $expr = "\"(?:{$char_str}|{$char_ref}|{$entity_ref})*\""; 524 return $expr; 525 } 526 527 function shortcode_re_attr($named=false) { 528 $name = shortcode_re_name(); 529 $literal = shortcode_re_literal(); 530 531 $expr = "{$name}(?:={$literal})?"; 532 if ($named) { 533 $expr = "(?P<attr>{$expr})"; 534 } 535 return $expr; 536 } 537 538 function shortcode_re_esc_lsqbr($named=false) { 539 $expr = "\\[\\["; 540 if ($named) { 541 $expr = "(?P<esc_lsqbr>{$expr})"; 542 } 543 return $expr; 544 } 545 546 function shortcode_re_esc_rsqbr($named=false) { 547 //two right square brackets not immediately followed by an odd number of right square brackets 548 $expr = '\]\](?=(?:\]\])*(?!\]))'; 549 if ($named) { 550 $expr = "(?P<esc_rsqbr>{$expr})"; 551 } 552 return $expr; 553 } 554 555 function shortcode_re_tag_inline($named=false) { 556 $name = shortcode_re_registered_name(); 557 $attr = shortcode_re_attr(); 558 559 $expr = "{$name}(?:\\s+{$attr})*"; 560 if ($named) { 561 $expr = "(?P<tag_inline>{$expr})"; 562 } 563 $expr = "\\[{$expr}\\s*\\/\\]"; 564 return $expr; 565 } 566 567 function shortcode_re_tag_open($named=false) { 568 $name = shortcode_re_registered_name(); 569 $attr = shortcode_re_attr(); 570 571 $expr = "{$name}(?:\\s+{$attr})*"; 572 if ($named) { 573 $expr = "(?P<tag_open>{$expr})"; 574 } 575 $expr = "\\[{$expr}\\s*\\]"; 576 return $expr; 577 } 578 579 function shortcode_re_tag_close($named=false) { 580 $name = shortcode_re_registered_name(); 581 $expr = $name; 582 if ($named) { 583 $expr = "(?P<tag_close>{$expr})"; 584 } 585 $expr = "\\[\\/{$expr}\\s*\\]"; 586 return $expr; 587 } 588 589 function shortcode_re_text($named=false) { 590 $expr = '(?s:.[^\[\]]*)'; 591 if ($named) { 592 $expr = "(?P<text>{$expr})"; 593 } 594 return $expr; 595 } 596 597 /** 598 * combines shortcode regex of types specified in parameters, returns delimited regex 599 */ 600 function shortcode_re_combine() { 601 $types = func_get_args(); 602 603 $regexps = array(); 604 foreach ($types as $type) { 605 $re_func = "shortcode_re_$type"; 606 array_push($regexps,$re_func(1)); 607 } 608 $statement = '/' . join('|',$regexps) . '/'; 609 return $statement; 610 } 611 612 /** 613 * returns regular expression to match subtypes of an expression (ex. attr => {$name}={$literal}) 614 */ 615 function shortcode_re_subtypes($type) { 616 $subtypes = shortcode_sub($type); 617 $re = null; 618 if (count($subtypes)) { 619 $re = call_user_func_array('shortcode_re_combine',$subtypes); 620 } 621 return $re; 622 } 623 624 /** 625 * evaluates a name expression 626 */ 627 function shortcode_eval_name($name) { 628 return strtolower($name['expression']); 629 } 630 631 /** 632 * evaluates a char_str expression 633 */ 634 function shortcode_eval_char_str($char_str) { 635 return $char_str['expression']; 636 } 637 638 /** 639 * did you get the point yet? 640 */ 641 function shortcode_eval_char_ref($char_ref) { 642 $expr = $char_ref['expression']; 643 if ($expr[0] == 'x') { 644 $expr = hexdec($expr); 645 } 646 $expr = chr($expr); 647 return $expr; 648 } 649 650 function shortcode_eval_entity_ref($entity_ref) { 651 $expr = strtolower($entity_ref['expression']); 652 $lookup = array_flip(get_html_translation_table(HTML_ENTITIES)); 653 return $lookup[$expr]; 654 } 655 656 function shortcode_eval_literal($literal) { 657 $expr = ""; 658 foreach ($literal['children'] as $child) { 659 $eval_func = "shortcode_eval_{$child['type']}"; 660 $expr .= $eval_func($child); 661 } 662 if ($expr[0] == "'" || $expr[0] == '"') { 663 $expr = substr($expr,1,strlen($expr)-2); 664 } 665 return $expr; 666 } 667 668 function shortcode_eval_attr($attr) { 669 $children = $attr['children']; 670 $key = shortcode_eval_name($children[0]); 671 if (isset($children[1])) { 672 $value = shortcode_eval_literal($children[1]); 673 } else { 674 $value = $key; 675 } 676 return array('key' => $key, 'value' => $value); 677 } 678 679 function shortcode_eval_tag_inline($tag_inline,&$stack) { 680 $children = $tag_inline['children']; 681 682 $name = shortcode_eval_name($children[0]); 683 $attrs = array(); 684 685 foreach ($children as $child) { 686 if ($child['type'] == 'attr') { 687 $attr = shortcode_eval_attr($child); 688 $key = $attr['key']; 689 $value = $attr['value']; 690 $attrs[$key] = $value; 691 } 692 } 693 694 $content = apply_shortcodes($attrs,'',$name); 695 696 // adds evaluated content to next on the stack 697 $node = array_pop($stack); 698 $node['content'] .= $content; 699 array_push($stack,$node); 700 701 return $node; 702 } 703 704 function shortcode_eval_esc_lsqbr($esc_lsqbr,&$stack) { 705 $node = array_pop($stack); 706 $node['content'] .= "["; 707 array_push($stack,$node); 708 709 return $node; 710 } 711 712 function shortcode_eval_esc_rsqbr($esc_rsqbr,&$stack) { 713 $node = array_pop($stack); 714 $node['content'] .= "]"; 715 array_push($stack,$node); 716 717 return $node; 718 } 719 720 function shortcode_eval_tag_open($tag_open,&$stack,&$refs) { 721 $children = $tag_open['children']; 722 723 $name = shortcode_eval_name($children[0]); 724 $attrs = array(); 725 726 foreach ($children as $child) { 727 if ($child['type'] == 'attr') { 728 $attr = shortcode_eval_attr($child); 729 $key = $attr['key']; 730 $value = $attr['value']; 731 $attrs[$key] = $value; 732 } 733 } 734 735 $data = array('name' => $name, 'attrs' => $attrs); 736 $node = array('node' => $data, 'content' => ''); 737 738 //throws itself on the stack with attributes, etc. 739 array_push($stack,$node); 740 $refs[$name] += 1; 741 742 return $node; 743 } 744 745 function shortcode_eval_tag_close($tag_close,&$stack,&$refs) { 746 $value = null; 747 748 $name = shortcode_eval_name($tag_close['children'][0]); 749 750 if ($refs[$name] > 0) { 751 do { 752 $child = array_pop($stack); 753 $child_node = $child['node']; 754 $child_name = $child['node']['name']; 755 $child_attrs = $child['node']['attrs']; 756 757 $parent = array_pop($stack); 758 $parent['content'] .= apply_shortcodes($child_attrs,'',$child_name); 759 $parent['content'] .= $child['content']; 760 761 $refs[$name] -= 1; 762 array_push($stack,$parent); 763 } while ($child_name != $name); 764 } 765 766 return array('name' => $name); 767 } 768 769 function shortcode_eval_text($text,&$stack) { 770 $node = array_pop($stack); 771 $node['content'] .= $text['expression']; 772 array_push($stack,$node); 773 return $text['expression']; 774 } 775 776 function shortcode_eval($expr) { 777 $stack = array(); 778 $refs = array(); 779 780 //build parse tree 781 $root = shortcode_build_tree($expr); 782 783 //create root content node, throw on stack 784 $root_node = array('node' => 'root', 'content' => ''); 785 array_push($stack,$root_node); 786 787 //evaluate immediate children 788 $subs = shortcode_sub(); 789 foreach ($root['children'] as $child) { 790 if (in_array($child['type'],$subs)) { 791 $func = "shortcode_eval_{$child['type']}"; 792 $func($child,$stack,$refs); 793 } 794 } 795 796 //evaluate children remaining on stack 797 $child = array_pop($stack); 798 while ($child['node'] != 'root') { 799 $parent = array_pop($stack); 800 801 $child_node = $child['node']; 802 $child_name = $child['node']['name']; 803 $child_attrs = $child['node']['attrs']; 804 805 $parent['content'] .= apply_shortcodes($child_attrs,'',$child_name); 806 $parent['content'] .= $child['content']; 807 $child = $parent; 808 } 809 810 return $child['content']; 811 } 812 813 /** 814 * builds a tree from a shortcode expression 815 */ 816 function shortcode_build_tree($parent) { 817 if (is_string($parent)) { 818 $parent = array('type' => 'root', 'expression' => $parent); 819 } 820 $parent_type = $parent['type']; 821 $parent_expression = $parent['expression']; 822 823 $re_subtypes = shortcode_re_subtypes($parent_type); 824 $nodes = array(); 825 if ($re_subtypes) { 826 $match_set = array(); 827 preg_match_all($re_subtypes,$parent_expression,$match_set,PREG_SET_ORDER); 828 829 foreach ($match_set as $index => $match) { 830 foreach (shortcode_types() as $type) { 831 if (strlen($match[$type]) > 0) { 832 $nodes[$index] = array('type' => $type, 'expression' => $match[$type]); 833 } 834 } 835 } 836 } 837 838 839 $parent['children'] = array_map('shortcode_build_tree',$nodes); 840 return $parent; 841 } 842 843 844 add_filter('the_content','shortcode_eval',11); 845 No newline at end of file