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—',
+ 'hello
world---',
+ 'hello
world—',
+ ),
+ );
+ }
+
+ /**
+ * 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---',
),
);
}