]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/shortcodes.php
WordPress 4.7.2-scripts
[autoinstalls/wordpress.git] / wp-includes / shortcodes.php
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  *     $out = do_shortcode( $content );
25  *
26  * @link https://codex.wordpress.org/Shortcode_API
27  *
28  * @package WordPress
29  * @subpackage Shortcodes
30  * @since 2.5.0
31  */
32
33 /**
34  * Container for storing shortcode tags and their hook to call for the shortcode
35  *
36  * @since 2.5.0
37  *
38  * @name $shortcode_tags
39  * @var array
40  * @global array $shortcode_tags
41  */
42 $shortcode_tags = array();
43
44 /**
45  * Add hook for shortcode tag.
46  *
47  * There can only be one hook for each shortcode. Which means that if another
48  * plugin has a similar shortcode, it will override yours or yours will override
49  * theirs depending on which order the plugins are included and/or ran.
50  *
51  * Simplest example of a shortcode tag using the API:
52  *
53  *     // [footag foo="bar"]
54  *     function footag_func( $atts ) {
55  *         return "foo = {
56  *             $atts[foo]
57  *         }";
58  *     }
59  *     add_shortcode( 'footag', 'footag_func' );
60  *
61  * Example with nice attribute defaults:
62  *
63  *     // [bartag foo="bar"]
64  *     function bartag_func( $atts ) {
65  *         $args = shortcode_atts( array(
66  *             'foo' => 'no foo',
67  *             'baz' => 'default baz',
68  *         ), $atts );
69  *
70  *         return "foo = {$args['foo']}";
71  *     }
72  *     add_shortcode( 'bartag', 'bartag_func' );
73  *
74  * Example with enclosed content:
75  *
76  *     // [baztag]content[/baztag]
77  *     function baztag_func( $atts, $content = '' ) {
78  *         return "content = $content";
79  *     }
80  *     add_shortcode( 'baztag', 'baztag_func' );
81  *
82  * @since 2.5.0
83  *
84  * @global array $shortcode_tags
85  *
86  * @param string   $tag  Shortcode tag to be searched in post content.
87  * @param callable $func Hook to run when shortcode is found.
88  */
89 function add_shortcode($tag, $func) {
90         global $shortcode_tags;
91
92         if ( '' == trim( $tag ) ) {
93                 $message = __( 'Invalid shortcode name: Empty name given.' );
94                 _doing_it_wrong( __FUNCTION__, $message, '4.4.0' );
95                 return;
96         }
97
98         if ( 0 !== preg_match( '@[<>&/\[\]\x00-\x20=]@', $tag ) ) {
99                 /* translators: 1: shortcode name, 2: space separated list of reserved characters */
100                 $message = sprintf( __( 'Invalid shortcode name: %1$s. Do not use spaces or reserved characters: %2$s' ), $tag, '& / < > [ ] =' );
101                 _doing_it_wrong( __FUNCTION__, $message, '4.4.0' );
102                 return;
103         }
104
105         $shortcode_tags[ $tag ] = $func;
106 }
107
108 /**
109  * Removes hook for shortcode.
110  *
111  * @since 2.5.0
112  *
113  * @global array $shortcode_tags
114  *
115  * @param string $tag Shortcode tag to remove hook for.
116  */
117 function remove_shortcode($tag) {
118         global $shortcode_tags;
119
120         unset($shortcode_tags[$tag]);
121 }
122
123 /**
124  * Clear all shortcodes.
125  *
126  * This function is simple, it clears all of the shortcode tags by replacing the
127  * shortcodes global by a empty array. This is actually a very efficient method
128  * for removing all shortcodes.
129  *
130  * @since 2.5.0
131  *
132  * @global array $shortcode_tags
133  */
134 function remove_all_shortcodes() {
135         global $shortcode_tags;
136
137         $shortcode_tags = array();
138 }
139
140 /**
141  * Whether a registered shortcode exists named $tag
142  *
143  * @since 3.6.0
144  *
145  * @global array $shortcode_tags List of shortcode tags and their callback hooks.
146  *
147  * @param string $tag Shortcode tag to check.
148  * @return bool Whether the given shortcode exists.
149  */
150 function shortcode_exists( $tag ) {
151         global $shortcode_tags;
152         return array_key_exists( $tag, $shortcode_tags );
153 }
154
155 /**
156  * Whether the passed content contains the specified shortcode
157  *
158  * @since 3.6.0
159  *
160  * @global array $shortcode_tags
161  *
162  * @param string $content Content to search for shortcodes.
163  * @param string $tag     Shortcode tag to check.
164  * @return bool Whether the passed content contains the given shortcode.
165  */
166 function has_shortcode( $content, $tag ) {
167         if ( false === strpos( $content, '[' ) ) {
168                 return false;
169         }
170
171         if ( shortcode_exists( $tag ) ) {
172                 preg_match_all( '/' . get_shortcode_regex() . '/', $content, $matches, PREG_SET_ORDER );
173                 if ( empty( $matches ) )
174                         return false;
175
176                 foreach ( $matches as $shortcode ) {
177                         if ( $tag === $shortcode[2] ) {
178                                 return true;
179                         } elseif ( ! empty( $shortcode[5] ) && has_shortcode( $shortcode[5], $tag ) ) {
180                                 return true;
181                         }
182                 }
183         }
184         return false;
185 }
186
187 /**
188  * Search content for shortcodes and filter shortcodes through their hooks.
189  *
190  * If there are no shortcode tags defined, then the content will be returned
191  * without any filtering. This might cause issues when plugins are disabled but
192  * the shortcode will still show up in the post or content.
193  *
194  * @since 2.5.0
195  *
196  * @global array $shortcode_tags List of shortcode tags and their callback hooks.
197  *
198  * @param string $content Content to search for shortcodes.
199  * @param bool $ignore_html When true, shortcodes inside HTML elements will be skipped.
200  * @return string Content with shortcodes filtered out.
201  */
202 function do_shortcode( $content, $ignore_html = false ) {
203         global $shortcode_tags;
204
205         if ( false === strpos( $content, '[' ) ) {
206                 return $content;
207         }
208
209         if (empty($shortcode_tags) || !is_array($shortcode_tags))
210                 return $content;
211
212         // Find all registered tag names in $content.
213         preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $content, $matches );
214         $tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] );
215
216         if ( empty( $tagnames ) ) {
217                 return $content;
218         }
219
220         $content = do_shortcodes_in_html_tags( $content, $ignore_html, $tagnames );
221
222         $pattern = get_shortcode_regex( $tagnames );
223         $content = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $content );
224
225         // Always restore square braces so we don't break things like <!--[if IE ]>
226         $content = unescape_invalid_shortcodes( $content );
227
228         return $content;
229 }
230
231 /**
232  * Retrieve the shortcode regular expression for searching.
233  *
234  * The regular expression combines the shortcode tags in the regular expression
235  * in a regex class.
236  *
237  * The regular expression contains 6 different sub matches to help with parsing.
238  *
239  * 1 - An extra [ to allow for escaping shortcodes with double [[]]
240  * 2 - The shortcode name
241  * 3 - The shortcode argument list
242  * 4 - The self closing /
243  * 5 - The content of a shortcode when it wraps some content.
244  * 6 - An extra ] to allow for escaping shortcodes with double [[]]
245  *
246  * @since 2.5.0
247  * @since 4.4.0 Added the `$tagnames` parameter.
248  *
249  * @global array $shortcode_tags
250  *
251  * @param array $tagnames Optional. List of shortcodes to find. Defaults to all registered shortcodes.
252  * @return string The shortcode search regular expression
253  */
254 function get_shortcode_regex( $tagnames = null ) {
255         global $shortcode_tags;
256
257         if ( empty( $tagnames ) ) {
258                 $tagnames = array_keys( $shortcode_tags );
259         }
260         $tagregexp = join( '|', array_map('preg_quote', $tagnames) );
261
262         // WARNING! Do not change this regex without changing do_shortcode_tag() and strip_shortcode_tag()
263         // Also, see shortcode_unautop() and shortcode.js.
264         return
265                   '\\['                              // Opening bracket
266                 . '(\\[?)'                           // 1: Optional second opening bracket for escaping shortcodes: [[tag]]
267                 . "($tagregexp)"                     // 2: Shortcode name
268                 . '(?![\\w-])'                       // Not followed by word character or hyphen
269                 . '('                                // 3: Unroll the loop: Inside the opening shortcode tag
270                 .     '[^\\]\\/]*'                   // Not a closing bracket or forward slash
271                 .     '(?:'
272                 .         '\\/(?!\\])'               // A forward slash not followed by a closing bracket
273                 .         '[^\\]\\/]*'               // Not a closing bracket or forward slash
274                 .     ')*?'
275                 . ')'
276                 . '(?:'
277                 .     '(\\/)'                        // 4: Self closing tag ...
278                 .     '\\]'                          // ... and closing bracket
279                 . '|'
280                 .     '\\]'                          // Closing bracket
281                 .     '(?:'
282                 .         '('                        // 5: Unroll the loop: Optionally, anything between the opening and closing shortcode tags
283                 .             '[^\\[]*+'             // Not an opening bracket
284                 .             '(?:'
285                 .                 '\\[(?!\\/\\2\\])' // An opening bracket not followed by the closing shortcode tag
286                 .                 '[^\\[]*+'         // Not an opening bracket
287                 .             ')*+'
288                 .         ')'
289                 .         '\\[\\/\\2\\]'             // Closing shortcode tag
290                 .     ')?'
291                 . ')'
292                 . '(\\]?)';                          // 6: Optional second closing brocket for escaping shortcodes: [[tag]]
293 }
294
295 /**
296  * Regular Expression callable for do_shortcode() for calling shortcode hook.
297  * @see get_shortcode_regex for details of the match array contents.
298  *
299  * @since 2.5.0
300  * @access private
301  *
302  * @global array $shortcode_tags
303  *
304  * @param array $m Regular expression match array
305  * @return string|false False on failure.
306  */
307 function do_shortcode_tag( $m ) {
308         global $shortcode_tags;
309
310         // allow [[foo]] syntax for escaping a tag
311         if ( $m[1] == '[' && $m[6] == ']' ) {
312                 return substr($m[0], 1, -1);
313         }
314
315         $tag = $m[2];
316         $attr = shortcode_parse_atts( $m[3] );
317
318         if ( ! is_callable( $shortcode_tags[ $tag ] ) ) {
319                 /* translators: %s: shortcode tag */
320                 $message = sprintf( __( 'Attempting to parse a shortcode without a valid callback: %s' ), $tag );
321                 _doing_it_wrong( __FUNCTION__, $message, '4.3.0' );
322                 return $m[0];
323         }
324
325         /**
326          * Filters whether to call a shortcode callback.
327          *
328          * Passing a truthy value to the filter will effectively short-circuit the
329          * shortcode generation process, returning that value instead.
330          *
331          * @since 4.7.0
332          *
333          * @param bool|string $return      Short-circuit return value. Either false or the value to replace the shortcode with.
334          * @param string      $tag         Shortcode name.
335          * @param array       $attr        Shortcode attributes array,
336          * @param array       $m           Regular expression match array.
337          */
338         $return = apply_filters( 'pre_do_shortcode_tag', false, $tag, $attr, $m );
339         if ( false !== $return ) {
340                 return $return;
341         }
342
343         $content = isset( $m[5] ) ? $m[5] : null;
344
345         $output = $m[1] . call_user_func( $shortcode_tags[ $tag ], $attr, $content, $tag ) . $m[6];
346
347         /**
348          * Filters the output created by a shortcode callback.
349          *
350          * @since 4.7.0
351          *
352          * @param string $output Shortcode output.
353          * @param string $tag    Shortcode name.
354          * @param array  $attr   Shortcode attributes array,
355          * @param array  $m      Regular expression match array.
356          */
357         return apply_filters( 'do_shortcode_tag', $output, $tag, $attr, $m );
358 }
359
360 /**
361  * Search only inside HTML elements for shortcodes and process them.
362  *
363  * Any [ or ] characters remaining inside elements will be HTML encoded
364  * to prevent interference with shortcodes that are outside the elements.
365  * Assumes $content processed by KSES already.  Users with unfiltered_html
366  * capability may get unexpected output if angle braces are nested in tags.
367  *
368  * @since 4.2.3
369  *
370  * @param string $content Content to search for shortcodes
371  * @param bool $ignore_html When true, all square braces inside elements will be encoded.
372  * @param array $tagnames List of shortcodes to find.
373  * @return string Content with shortcodes filtered out.
374  */
375 function do_shortcodes_in_html_tags( $content, $ignore_html, $tagnames ) {
376         // Normalize entities in unfiltered HTML before adding placeholders.
377         $trans = array( '&#91;' => '&#091;', '&#93;' => '&#093;' );
378         $content = strtr( $content, $trans );
379         $trans = array( '[' => '&#91;', ']' => '&#93;' );
380
381         $pattern = get_shortcode_regex( $tagnames );
382         $textarr = wp_html_split( $content );
383
384         foreach ( $textarr as &$element ) {
385                 if ( '' == $element || '<' !== $element[0] ) {
386                         continue;
387                 }
388
389                 $noopen = false === strpos( $element, '[' );
390                 $noclose = false === strpos( $element, ']' );
391                 if ( $noopen || $noclose ) {
392                         // This element does not contain shortcodes.
393                         if ( $noopen xor $noclose ) {
394                                 // Need to encode stray [ or ] chars.
395                                 $element = strtr( $element, $trans );
396                         }
397                         continue;
398                 }
399
400                 if ( $ignore_html || '<!--' === substr( $element, 0, 4 ) || '<![CDATA[' === substr( $element, 0, 9 ) ) {
401                         // Encode all [ and ] chars.
402                         $element = strtr( $element, $trans );
403                         continue;
404                 }
405
406                 $attributes = wp_kses_attr_parse( $element );
407                 if ( false === $attributes ) {
408                         // Some plugins are doing things like [name] <[email]>.
409                         if ( 1 === preg_match( '%^<\s*\[\[?[^\[\]]+\]%', $element ) ) {
410                                 $element = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $element );
411                         }
412
413                         // Looks like we found some crazy unfiltered HTML.  Skipping it for sanity.
414                         $element = strtr( $element, $trans );
415                         continue;
416                 }
417
418                 // Get element name
419                 $front = array_shift( $attributes );
420                 $back = array_pop( $attributes );
421                 $matches = array();
422                 preg_match('%[a-zA-Z0-9]+%', $front, $matches);
423                 $elname = $matches[0];
424
425                 // Look for shortcodes in each attribute separately.
426                 foreach ( $attributes as &$attr ) {
427                         $open = strpos( $attr, '[' );
428                         $close = strpos( $attr, ']' );
429                         if ( false === $open || false === $close ) {
430                                 continue; // Go to next attribute.  Square braces will be escaped at end of loop.
431                         }
432                         $double = strpos( $attr, '"' );
433                         $single = strpos( $attr, "'" );
434                         if ( ( false === $single || $open < $single ) && ( false === $double || $open < $double ) ) {
435                                 // $attr like '[shortcode]' or 'name = [shortcode]' implies unfiltered_html.
436                                 // In this specific situation we assume KSES did not run because the input
437                                 // was written by an administrator, so we should avoid changing the output
438                                 // and we do not need to run KSES here.
439                                 $attr = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $attr );
440                         } else {
441                                 // $attr like 'name = "[shortcode]"' or "name = '[shortcode]'"
442                                 // We do not know if $content was unfiltered. Assume KSES ran before shortcodes.
443                                 $count = 0;
444                                 $new_attr = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $attr, -1, $count );
445                                 if ( $count > 0 ) {
446                                         // Sanitize the shortcode output using KSES.
447                                         $new_attr = wp_kses_one_attr( $new_attr, $elname );
448                                         if ( '' !== trim( $new_attr ) ) {
449                                                 // The shortcode is safe to use now.
450                                                 $attr = $new_attr;
451                                         }
452                                 }
453                         }
454                 }
455                 $element = $front . implode( '', $attributes ) . $back;
456
457                 // Now encode any remaining [ or ] chars.
458                 $element = strtr( $element, $trans );
459         }
460
461         $content = implode( '', $textarr );
462
463         return $content;
464 }
465
466 /**
467  * Remove placeholders added by do_shortcodes_in_html_tags().
468  *
469  * @since 4.2.3
470  *
471  * @param string $content Content to search for placeholders.
472  * @return string Content with placeholders removed.
473  */
474 function unescape_invalid_shortcodes( $content ) {
475         // Clean up entire string, avoids re-parsing HTML.
476         $trans = array( '&#91;' => '[', '&#93;' => ']' );
477         $content = strtr( $content, $trans );
478
479         return $content;
480 }
481
482 /**
483  * Retrieve the shortcode attributes regex.
484  *
485  * @since 4.4.0
486  *
487  * @return string The shortcode attribute regular expression
488  */
489 function get_shortcode_atts_regex() {
490         return '/([\w-]+)\s*=\s*"([^"]*)"(?:\s|$)|([\w-]+)\s*=\s*\'([^\']*)\'(?:\s|$)|([\w-]+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/';
491 }
492
493 /**
494  * Retrieve all attributes from the shortcodes tag.
495  *
496  * The attributes list has the attribute name as the key and the value of the
497  * attribute as the value in the key/value pair. This allows for easier
498  * retrieval of the attributes, since all attributes have to be known.
499  *
500  * @since 2.5.0
501  *
502  * @param string $text
503  * @return array|string List of attribute values.
504  *                      Returns empty array if trim( $text ) == '""'.
505  *                      Returns empty string if trim( $text ) == ''.
506  *                      All other matches are checked for not empty().
507  */
508 function shortcode_parse_atts($text) {
509         $atts = array();
510         $pattern = get_shortcode_atts_regex();
511         $text = preg_replace("/[\x{00a0}\x{200b}]+/u", " ", $text);
512         if ( preg_match_all($pattern, $text, $match, PREG_SET_ORDER) ) {
513                 foreach ($match as $m) {
514                         if (!empty($m[1]))
515                                 $atts[strtolower($m[1])] = stripcslashes($m[2]);
516                         elseif (!empty($m[3]))
517                                 $atts[strtolower($m[3])] = stripcslashes($m[4]);
518                         elseif (!empty($m[5]))
519                                 $atts[strtolower($m[5])] = stripcslashes($m[6]);
520                         elseif (isset($m[7]) && strlen($m[7]))
521                                 $atts[] = stripcslashes($m[7]);
522                         elseif (isset($m[8]))
523                                 $atts[] = stripcslashes($m[8]);
524                 }
525
526                 // Reject any unclosed HTML elements
527                 foreach( $atts as &$value ) {
528                         if ( false !== strpos( $value, '<' ) ) {
529                                 if ( 1 !== preg_match( '/^[^<]*+(?:<[^>]*+>[^<]*+)*+$/', $value ) ) {
530                                         $value = '';
531                                 }
532                         }
533                 }
534         } else {
535                 $atts = ltrim($text);
536         }
537         return $atts;
538 }
539
540 /**
541  * Combine user attributes with known attributes and fill in defaults when needed.
542  *
543  * The pairs should be considered to be all of the attributes which are
544  * supported by the caller and given as a list. The returned attributes will
545  * only contain the attributes in the $pairs list.
546  *
547  * If the $atts list has unsupported attributes, then they will be ignored and
548  * removed from the final returned list.
549  *
550  * @since 2.5.0
551  *
552  * @param array  $pairs     Entire list of supported attributes and their defaults.
553  * @param array  $atts      User defined attributes in shortcode tag.
554  * @param string $shortcode Optional. The name of the shortcode, provided for context to enable filtering
555  * @return array Combined and filtered attribute list.
556  */
557 function shortcode_atts( $pairs, $atts, $shortcode = '' ) {
558         $atts = (array)$atts;
559         $out = array();
560         foreach ($pairs as $name => $default) {
561                 if ( array_key_exists($name, $atts) )
562                         $out[$name] = $atts[$name];
563                 else
564                         $out[$name] = $default;
565         }
566         /**
567          * Filters a shortcode's default attributes.
568          *
569          * If the third parameter of the shortcode_atts() function is present then this filter is available.
570          * The third parameter, $shortcode, is the name of the shortcode.
571          *
572          * @since 3.6.0
573          * @since 4.4.0 Added the `$shortcode` parameter.
574          *
575          * @param array  $out       The output array of shortcode attributes.
576          * @param array  $pairs     The supported attributes and their defaults.
577          * @param array  $atts      The user defined shortcode attributes.
578          * @param string $shortcode The shortcode name.
579          */
580         if ( $shortcode ) {
581                 $out = apply_filters( "shortcode_atts_{$shortcode}", $out, $pairs, $atts, $shortcode );
582         }
583
584         return $out;
585 }
586
587 /**
588  * Remove all shortcode tags from the given content.
589  *
590  * @since 2.5.0
591  *
592  * @global array $shortcode_tags
593  *
594  * @param string $content Content to remove shortcode tags.
595  * @return string Content without shortcode tags.
596  */
597 function strip_shortcodes( $content ) {
598         global $shortcode_tags;
599
600         if ( false === strpos( $content, '[' ) ) {
601                 return $content;
602         }
603
604         if (empty($shortcode_tags) || !is_array($shortcode_tags))
605                 return $content;
606
607         // Find all registered tag names in $content.
608         preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $content, $matches );
609
610         $tags_to_remove = array_keys( $shortcode_tags );
611
612         /**
613          * Filters the list of shortcode tags to remove from the content.
614          *
615          * @since 4.7.0
616          *
617          * @param array  $tag_array Array of shortcode tags to remove.
618          * @param string $content   Content shortcodes are being removed from.
619          */
620         $tags_to_remove = apply_filters( 'strip_shortcodes_tagnames', $tags_to_remove, $content );
621
622         $tagnames = array_intersect( $tags_to_remove, $matches[1] );
623
624         if ( empty( $tagnames ) ) {
625                 return $content;
626         }
627
628         $content = do_shortcodes_in_html_tags( $content, true, $tagnames );
629
630         $pattern = get_shortcode_regex( $tagnames );
631         $content = preg_replace_callback( "/$pattern/", 'strip_shortcode_tag', $content );
632
633         // Always restore square braces so we don't break things like <!--[if IE ]>
634         $content = unescape_invalid_shortcodes( $content );
635
636         return $content;
637 }
638
639 /**
640  * Strips a shortcode tag based on RegEx matches against post content.
641  *
642  * @since 3.3.0
643  *
644  * @param array $m RegEx matches against post content.
645  * @return string|false The content stripped of the tag, otherwise false.
646  */
647 function strip_shortcode_tag( $m ) {
648         // allow [[foo]] syntax for escaping a tag
649         if ( $m[1] == '[' && $m[6] == ']' ) {
650                 return substr($m[0], 1, -1);
651         }
652
653         return $m[1] . $m[6];
654 }