WordPress.org

Make WordPress Core

Ticket #14481: shortcode.patch

File shortcode.patch, 28.4 KB (added by deadowl, 4 years ago)

shortcode patch

  • shortcodes.php

     
    11<?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/* 
     3Author: Jacob Beauregard (jrbeaure at uvm dot edu) 
     4*/ 
    345 
     6/* 
     7 
     8From Test Cases (http://svn.automattic.com/wordpress-tests/wp-testcase/test_shortcode.php) 
     9========================================================================================== 
     10Shortcode 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 
     17Major Differences/Improvements: 
     18=============================== 
     19I. 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 
     28II. 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 
     39III. 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 
     45IV. 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 
     64VI. Addressing Tickets #9264, #10082 (see following, `Basic Interpretation Method`) 
     65 
     66 
     67Basic Interpretation Method: 
     68============================ 
     691. If an open tag is encountered, it is added to the stack 
     70 
     712. If a close tag is encountered and there is no matching open tag on the stack 
     72   the close tag is ignored 
     73 
     743. 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 
     774. If text or an inline tag is encountered, it will be evaluated to its parent's 
     78   content immediately 
     79 
     805. If tags are not closed by the end of the interpretation, they will be implicitly 
     81   self-closed 
     82 
     83 
     84Issues: 
     85======= 
     861. Haven't written new unit tests to reflect new functionality added 
     87 
     882. Documentation is not as good (though I hope most of the code is self-explanatory) 
     89 
     903. Not 100% backwards compatible 
     91 
     92*/ 
     93 
     94/* +++++ PUBLIC FUNCTIONS ++++++ */ 
     95 
    3596/** 
    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*/ 
     99function 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} 
    44104 
    45105/** 
    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*/ 
     108function remove_shortcode($name,$handler) { 
     109        $name = strtolower($name); 
     110        $priority = shortcode_register_get('priority',$name,$handler); 
    95111 
    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); 
    98114} 
    99115 
    100116/** 
    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*/ 
     119function has_shortcode($name,$handler) { 
     120        $name = strtolower($name); 
     121        return shortcode_register_get('priority',$name,$handler); 
     122} 
    110123 
    111         unset($shortcode_tags[$tag]); 
     124/** 
     125 * removes all shortcode hooks 
     126*/ 
     127function remove_all_shortcodes($name) { 
     128        $name = strtolower($name); 
     129        shortcode_register_unset('priority',$name); 
     130        shortcode_register_unset('callback',$name); 
    112131} 
    113132 
    114133/** 
    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*/ 
     137function 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} 
    126148 
    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*/ 
     153function 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; 
    128166} 
    129167 
    130168/** 
    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*/ 
     171function 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} 
    146176 
    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*/ 
     180function shortcode_atts($defaults, $attrs) { 
     181        return array_merge($defaults, $attrs); 
     182} 
    149183 
    150         $pattern = get_shortcode_regex(); 
    151         return preg_replace_callback('/'.$pattern.'/s', 'do_shortcode_tag', $content); 
     184/* run main eval function */ 
     185function do_shortcode($expr) { 
     186        return shortcode_eval($expr); 
    152187} 
    153188 
     189 
     190 
     191/* +++++++++++++ THE UNDERPINNINGS +++++++++++ */ 
     192 
    154193/** 
    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*/ 
     196function shortcode_tags() { 
     197        $tags = array_keys(shortcode_register_get('callback')); 
     198        return $tags; 
     199} 
    177200 
    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. 
     205function shortcode_register($assignment=null) { 
     206        static $register = array(); 
     207        if ($assignment !== null) { 
     208                $register = $assignment; 
     209        } 
     210        return $register; 
    180211} 
    181212 
    182213/** 
    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*/ 
     216function shortcode_register_get() { 
     217        $arguments = func_get_args(); 
     218        $register  = shortcode_register(); 
    195219 
    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                } 
    199228        } 
     229        return $value; 
     230} 
    200231 
    201         $tag = $m[2]; 
    202         $attr = shortcode_parse_atts( $m[3] ); 
     232/** 
     233 * sets a value in the register 
     234*/ 
     235function shortcode_register_set() { 
     236        $arguments = func_get_args(); 
     237        $register  = shortcode_register(); 
     238        $value     = array_pop($arguments); 
    203239 
    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]; 
    210247        } 
     248        $group = $value; 
     249        shortcode_register($register); 
    211250} 
    212251 
    213252/** 
    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*/ 
     255function 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]; 
    241267                } 
    242         } else { 
    243                 $atts = ltrim($text); 
    244268        } 
    245         return $atts; 
     269        if ($exists) { 
     270                $key = array_shift($arguments); 
     271                unset($group[$key]); 
     272        } 
     273        shortcode_register($register); 
    246274} 
    247275 
     276/* 
     277 * types of shortcodes to be parsed 
     278*/ 
     279function 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*/ 
     299function 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*/ 
     306function shortcode_sub_tag_inline() { 
     307        return array('attr','name'); 
     308} 
     309 
     310/* 
     311 * i bet you get the point by now 
     312*/ 
     313function shortcode_sub_tag_open() { 
     314        return array('attr','name'); 
     315} 
     316 
     317function shortcode_sub_tag_close() { 
     318        return array('name'); 
     319} 
     320 
     321function shortcode_sub_text() { 
     322        return null; 
     323} 
     324 
     325function shortcode_sub_name() { 
     326        return null; 
     327} 
     328 
     329function shortcode_sub_attr() { 
     330        return array('literal','name'); 
     331} 
     332 
     333function shortcode_sub_literal() { 
     334        return array('char_ref','entity_ref','char_str'); 
     335} 
     336 
     337function shortcode_sub_char_str() { 
     338        return null; 
     339} 
     340 
     341function shortcode_sub_char_ref() { 
     342        return null; 
     343} 
     344 
     345function 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 
     350function shortcode_sub_esc_lsqbr() { 
     351        return null; 
     352} 
     353 
     354function shortcode_sub_esc_rsqbr() { 
     355        return null; 
     356} 
     357 
    248358/** 
    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*/ 
     361function shortcode_sub($type='root') { 
     362        if (in_array($type,shortcode_types()) || $type == 'root') { 
     363                $func = "shortcode_sub_{$type}"; 
     364                $subtypes = $func(); 
    272365        } 
    273         return $out; 
     366        return $subtypes; 
    274367} 
    275368 
     369 
     370// started writing backwards compatibility function 
     371// but would really prefer not to support these 
     372 
     373function shortcode_re_compat_name($named=false) { 
     374        $expr = '\w+'; 
     375        if ($named) { 
     376                $expr = "(?P<compat_name>{$expr})"; 
     377        } 
     378        return $expr; 
     379} 
     380 
     381function 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 
     392function shortcode_re_compat_literal_dq() { 
     393        $expr = '"[^"]*"'; 
     394        return $expr; 
     395} 
     396 
     397function shortcode_re_compat_literal_sq() { 
     398        $expr = "'[^']*'"; 
     399        return $expr; 
     400} 
     401 
     402function shortcode_re_compat_literal_nq() { 
     403        $expr = '\S+?(?=\])'; 
     404        return $expr; 
     405} 
     406 
     407function 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 
    276417/** 
    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*/ 
     420function 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} 
    287427 
    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*/ 
     431function 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} 
    290440 
    291         $pattern = get_shortcode_regex(); 
     441/** 
     442 * you probably get the point by now 
     443*/ 
     444function shortcode_re_char_str($named=false) { 
     445        $expr =  "[^%&]+"; 
     446        if ($named) { 
     447                $expr = "(?P<char_str>{$expr})"; 
     448        } 
     449        return $expr; 
     450} 
    292451 
    293         return preg_replace('/'.$pattern.'/s', '$1$6', $content); 
     452/** 
     453 * single quote version for shortcode_re_char_str 
     454*/ 
     455function shortcode_re_char_str_sq($named=false) { 
     456        $expr =  "[^%&']+"; 
     457        if ($named) { 
     458                $expr = "(?P<char_str>{$expr})"; 
     459        } 
     460        return $expr; 
    294461} 
    295462 
    296 add_filter('the_content', 'do_shortcode', 11); // AFTER wpautop() 
     463/** 
     464 * double quote version for shortcode_re_char_str 
     465*/ 
     466function shortcode_re_char_str_dq($named=false) { 
     467        $expr = '[^%&"]+'; 
     468        if ($named) { 
     469                $expr = "(?P<char_str>{$expr})"; 
     470        } 
     471        return $expr; 
     472} 
    297473 
    298 ?> 
    299  No newline at end of file 
     474function 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 
     483function 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 
     493function 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 
     504function shortcode_re_literal_nq($named=false) { 
     505        $expr = "[-a-zA-Z0-9_:.]+"; 
     506        return $expr; 
     507} 
     508 
     509function 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 
     518function 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 
     527function 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 
     538function shortcode_re_esc_lsqbr($named=false) { 
     539        $expr = "\\[\\["; 
     540        if ($named) { 
     541                $expr = "(?P<esc_lsqbr>{$expr})"; 
     542        } 
     543        return $expr; 
     544} 
     545 
     546function 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 
     555function 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 
     567function 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 
     579function 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 
     589function 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*/ 
     600function 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*/ 
     615function 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*/ 
     627function shortcode_eval_name($name) { 
     628        return strtolower($name['expression']); 
     629} 
     630 
     631/** 
     632 * evaluates a char_str expression 
     633*/ 
     634function shortcode_eval_char_str($char_str) { 
     635        return $char_str['expression']; 
     636} 
     637 
     638/** 
     639 * did you get the point yet? 
     640*/ 
     641function 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 
     650function 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 
     656function 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 
     668function 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 
     679function 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 
     704function 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 
     712function 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 
     720function 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 
     745function 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 
     769function 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 
     776function 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*/ 
     816function 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 
     844add_filter('the_content','shortcode_eval',11); 
     845 No newline at end of file