diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index 4daeba9a44..0f204b8931 100644 --- a/src/wp-includes/formatting.php +++ b/src/wp-includes/formatting.php @@ -28,7 +28,7 @@ * @return string The string replaced with html entities */ function wptexturize($text, $reset = false) { - global $wp_cockneyreplace; + global $wp_cockneyreplace, $shortcode_tags; static $static_characters, $static_replacements, $dynamic_characters, $dynamic_replacements, $default_no_texturize_tags, $default_no_texturize_shortcodes, $run_texturize = true; @@ -205,6 +205,10 @@ function wptexturize($text, $reset = false) { // Look for shortcodes and HTML elements. + $tagnames = array_keys( $shortcode_tags ); + $tagregexp = join( '|', array_map( 'preg_quote', $tagnames ) ); + $tagregexp = "(?:$tagregexp)(?![\\w-])"; // Excerpt of get_shortcode_regex(). + $regex = '/(' // Capture the entire match. . '<' // Find start of element. . '(?(?=!--)' // Is this a comment? @@ -215,11 +219,9 @@ function wptexturize($text, $reset = false) { . '|' . '\[' // Find start of shortcode. . '\[?' // Shortcodes may begin with [[ - . '(?:' - . '[^\[\]<>]' // Shortcodes do not contain other shortcodes. - . '|' - . '<[^>]+>' // HTML elements permitted. Prevents matching ] before >. - . ')++' + . '\/?' // Closing slash may precede name. + . $tagregexp // Only match registered shortcodes, because performance. + . '[^\[\]]*' // Shortcodes do not contain other shortcodes. . '\]' // Find end of shortcode. . '\]?' // Shortcodes may end with ]] . ')/s'; @@ -241,18 +243,18 @@ function wptexturize($text, $reset = false) { continue; - } elseif ( '[' === $first && 1 === preg_match( '/^\[(?:[^\[\]<>]|<[^>]+>)++\]$/', $curl ) ) { + } elseif ( '[' === $first && 1 === preg_match( '/^\[\[?\/?' . $tagregexp . '[^\[\]]*\]\]?$/', $curl ) ) { // This is a shortcode delimiter. - _wptexturize_pushpop_element( $curl, $no_texturize_shortcodes_stack, $no_texturize_shortcodes ); - - } elseif ( '[' === $first && 1 === preg_match( '/^\[\[?(?:[^\[\]<>]|<[^>]+>)++\]\]?$/', $curl ) ) { - // This is an escaped shortcode delimiter. - - // Do not texturize. - // Do not push to the shortcodes stack. - - continue; + if ( '[[' !== substr( $curl, 0, 2 ) && ']]' !== substr( $curl, -2 ) ) { + // Looks like a normal shortcode. + _wptexturize_pushpop_element( $curl, $no_texturize_shortcodes_stack, $no_texturize_shortcodes ); + } else { + // Looks like an escaped shortcode. + // Do not texturize. + // Do not push to the shortcodes stack. + continue; + } } elseif ( empty( $no_texturize_shortcodes_stack ) && empty( $no_texturize_tags_stack ) ) { // This is neither a delimiter, nor is this content inside of no_texturize pairs. Do texturize. @@ -313,7 +315,7 @@ function _wptexturize_pushpop_element($text, &$stack, $disabled_elements) { // Parse out the tag name. $space = strpos( $text, ' ' ); - if ( FALSE === $space ) { + if ( false === $space ) { $space = -1; } else { $space -= $name_offset; diff --git a/src/wp-includes/shortcodes.php b/src/wp-includes/shortcodes.php index 55e64a1863..c8e5e2657b 100644 --- a/src/wp-includes/shortcodes.php +++ b/src/wp-includes/shortcodes.php @@ -231,7 +231,7 @@ function get_shortcode_regex() { $tagregexp = join( '|', array_map('preg_quote', $tagnames) ); // WARNING! Do not change this regex without changing do_shortcode_tag() and strip_shortcode_tag() - // Also, see shortcode_unautop() and shortcode.js. + // Also, see shortcode_unautop() and shortcode.js and wptexturize(). return '\\[' // Opening bracket . '(\\[?)' // 1: Optional second opening bracket for escaping shortcodes: [[tag]] diff --git a/tests/phpunit/tests/formatting/WPTexturize.php b/tests/phpunit/tests/formatting/WPTexturize.php index 01dfa00c05..4babe1f1ed 100644 --- a/tests/phpunit/tests/formatting/WPTexturize.php +++ b/tests/phpunit/tests/formatting/WPTexturize.php @@ -11,7 +11,6 @@ class Tests_Formatting_WPTexturize extends WP_UnitTestCase { function test_disable() { $this->assertEquals('
---
', wptexturize('
---
')); - $this->assertEquals('[a]a–b[code]---[/code]a–b[/a]', wptexturize('[a]a--b[code]---[/code]a--b[/a]')); $this->assertEquals('
--
', wptexturize('
--
')); $this->assertEquals( '---', wptexturize( '---' ) ); @@ -1209,28 +1208,20 @@ class Tests_Formatting_WPTexturize extends WP_UnitTestCase { '[gallery ...]]', ), array( - '[/...]', // This would actually be ignored by the shortcode system. The decision to not texturize it is intentional, if not correct. - '[/...]', + '[/gallery ...]', // This would actually be ignored by the shortcode system. The decision to not texturize it is intentional, if not correct. + '[/gallery ...]', ), array( '[...]...[/...]', // These are potentially usable shortcodes. - '[...]…[/...]', + '[…]…[/…]', ), array( - '[[...]]...[[/...]]', // Shortcode parsing will ignore the inner ]...[ part and treat this as a single escaped shortcode. - '[[...]]…[[/...]]', + '[[gallery]]...[[/gallery]]', // Shortcode parsing will ignore the inner ]...[ part and treat this as a single escaped shortcode. + '[[gallery]]…[[/gallery]]', ), array( - '[[[...]]]...[[[/...]]]', // Again, shortcode parsing matches, but only the [[...] and [/...]] parts. - '[[[...]]]…[[[/...]]]', - ), - array( - '[[code]...[/code]...', // These are potentially usable shortcodes. Unfortunately, the meaning of [[code] is ambiguous unless we run the entire shortcode regexp. - '[[code]…[/code]…', - ), - array( - '[code]...[/code]]...', // These are potentially usable shortcodes. Unfortunately, the meaning of [/code]] is ambiguous unless we run the entire shortcode regexp. - '[code]...[/code]]...', // This test would not pass in 3.9 because the extra brace was always ignored by texturize. + '[[[gallery]]]...[[[/gallery]]]', // Again, shortcode parsing matches, but only the [[gallery] and [/gallery]] parts. + '[[[gallery]]]…[[[/gallery]]]', ), array( '[gal>ery ...]', @@ -1345,8 +1336,8 @@ class Tests_Formatting_WPTexturize extends WP_UnitTestCase { '[ but also catches the styled “[quote]” here ]', ), array( - '[Let\'s get crazy[plugin code="hello"]world]', - '[Let’s get crazy[plugin code="hello"]world]', + '[Let\'s get crazy[caption code="hello"]world]', // caption shortcode is invalid here because it contains [] chars. + '[Let’s get crazy[caption code=”hello“]world]', ), ); } @@ -1698,32 +1689,85 @@ class Tests_Formatting_WPTexturize extends WP_UnitTestCase { 'hello---', ), array( - 'hello[/code]---', - 'hello[/code]—', + 'hello---', + 'hello', ), array( - '[/code]hello---', - '[/code]hello', + 'helloworld---', + 'helloworld', + ), + ); + } + + /** + * Test disabling shortcode texturization. + * + * @ticket 29557 + * @dataProvider data_unregistered_shortcodes + */ + function test_unregistered_shortcodes( $input, $output ) { + add_filter( 'no_texturize_shortcodes', array( $this, 'filter_shortcodes' ), 10, 1 ); + + $output = $this->assertEquals( $output, wptexturize( $input ) ); + + remove_filter( 'no_texturize_shortcodes', array( $this, 'filter_shortcodes' ), 10, 1 ); + return $output; + } + + function filter_shortcodes( $disabled ) { + $disabled[] = 'audio'; + return $disabled; + } + + function data_unregistered_shortcodes() { + return array( + array( + '[a]a--b[audio]---[/audio]a--b[/a]', + '[a]a–b[audio]---[/audio]a–b[/a]', ), array( - '[code]hello[/code]---', - '[code]hello[/code]—', + '[code ...]...[/code]', // code is not a registered shortcode. + '[code …]…[/code]', ), array( - 'hello---[code]', - 'hello—[code]', + '[hello ...]...[/hello]', // hello is not a registered shortcode. + '[hello …]…[/hello]', ), array( - 'hello[code]---', - 'hello[code]---', + '[[audio]...[/audio]...', // These are potentially usable shortcodes. Unfortunately, the meaning of [[audio] is ambiguous unless we run the entire shortcode regexp. + '[[audio]…[/audio]…', ), array( - '[code]hello---', - '[code]hello---', + '[audio]...[/audio]]...', // These are potentially usable shortcodes. Unfortunately, the meaning of [/audio]] is ambiguous unless we run the entire shortcode regexp. + '[audio]...[/audio]]...', // This test would not pass in 3.9 because the extra brace was always ignored by texturize. ), array( - '[code]hello---', - '[code]hello---', + 'hello[/audio]---', + 'hello[/audio]—', + ), + array( + '[/audio]hello---', + '[/audio]hello', + ), + array( + '[audio]hello[/audio]---', + '[audio]hello[/audio]—', + ), + array( + 'hello---[audio]', + 'hello—[audio]', + ), + array( + 'hello[audio]---', + 'hello[audio]---', + ), + array( + '[audio]hello---', + '[audio]hello---', + ), + array( + '[audio]hello---', + '[audio]hello---', ), ); }