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