WordPress.org

Make WordPress Core

Ticket #35022: 35022.6.patch

File 35022.6.patch, 13.0 KB (added by gitlost, 3 years ago)

Refresh after [39351] (#38914).

  • src/wp-includes/formatting.php

     
    2525 * @since 0.71
    2626 *
    2727 * @global array $wp_cockneyreplace Array of formatted entities for certain common phrases
    28  * @global array $shortcode_tags
    2928 * @staticvar array $static_characters
    3029 * @staticvar array $static_replacements
    3130 * @staticvar array $dynamic_characters
     
    3938 * @return string The string replaced with html entities
    4039 */
    4140function wptexturize( $text, $reset = false ) {
    42         global $wp_cockneyreplace, $shortcode_tags;
     41        global $wp_cockneyreplace;
    4342        static $static_characters = null,
    4443                $static_replacements = null,
    4544                $dynamic_characters = null,
     
    216215
    217216        // Look for shortcodes and HTML elements.
    218217
    219         preg_match_all( '@\[/?([^<>&/\[\]\x00-\x20=]++)@', $text, $matches );
    220         $tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] );
     218        $tagnames = get_shortcode_tagnames( $text );
    221219        $found_shortcodes = ! empty( $tagnames );
    222220        $shortcode_regex = $found_shortcodes ? _get_wptexturize_shortcode_regex( $tagnames ) : '';
    223221        $regex = _get_wptexturize_split_regex( $shortcode_regex );
     
    702700 */
    703701function _get_wptexturize_shortcode_regex( $tagnames ) {
    704702        $tagregexp = join( '|', array_map( 'preg_quote', $tagnames ) );
    705         $tagregexp = "(?:$tagregexp)(?=[\\s\\]\\/])"; // Excerpt of get_shortcode_regex().
     703        $tagregexp = "(?:$tagregexp)(?=" . shortcode_name_terminators() . ")"; // Excerpt of get_shortcode_regex().
    706704        $regex =
    707705                  '\['              // Find start of shortcode.
    708706                . '[\/\[]?'         // Shortcodes may begin with [/ or [[
     
    788786 *
    789787 * @since 2.9.0
    790788 *
    791  * @global array $shortcode_tags
    792  *
    793789 * @param string $pee The content.
    794790 * @return string The filtered content.
    795791 */
    796792function shortcode_unautop( $pee ) {
    797         global $shortcode_tags;
    798793
    799         if ( empty( $shortcode_tags ) || !is_array( $shortcode_tags ) ) {
     794        $tagnames = get_shortcode_tagnames( $pee );
     795        if ( empty( $tagnames ) ) {
    800796                return $pee;
    801797        }
    802798
    803         $tagregexp = join( '|', array_map( 'preg_quote', array_keys( $shortcode_tags ) ) );
     799        $tagregexp = join( '|', array_map( 'preg_quote', $tagnames ) );
    804800        $spaces = wp_spaces_regexp();
    805801
    806802        $pattern =
     
    810806                . '('                                // 1: The shortcode
    811807                .     '\\['                          // Opening bracket
    812808                .     "($tagregexp)"                 // 2: Shortcode name
    813                 .     '(?![\\w-])'                   // Not followed by word character or hyphen
     809                .     '(?=' . shortcode_name_terminators() . ')' // Followed by a shortcode name terminator.
    814810                                                     // Unroll the loop: Inside the opening shortcode tag
    815811                .     '[^\\]\\/]*'                   // Not a closing bracket or forward slash
    816812                .     '(?:'
  • src/wp-includes/js/shortcode.js

     
    102102                // 6. The closing tag.
    103103                // 7. An extra `]` to allow for escaping shortcodes with double `[[]]`
    104104                regexp: _.memoize( function( tag ) {
    105                         return new RegExp( '\\[(\\[?)(' + tag + ')(?![\\w-])([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)(\\[\\/\\2\\]))?)(\\]?)', 'g' );
     105                        var shortcode_name_terminators = '[<>&\\/\\[\\]\x00-\x20=\u00a0]';
     106                        return new RegExp( '\\[(\\[?)(' + tag + ')(?=' + shortcode_name_terminators + ')([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)(\\[\\/\\2\\]))?)(\\]?)', 'g' );
    106107                }),
    107108
    108109
  • src/wp-includes/shortcodes.php

     
    9595                return;
    9696        }
    9797
    98         if ( 0 !== preg_match( '@[<>&/\[\]\x00-\x20=]@', $tag ) ) {
     98        if ( 0 !== preg_match( '@' . shortcode_name_terminators() . '@', $tag ) ) {
    9999                /* translators: 1: shortcode name, 2: space separated list of reserved characters */
    100100                $message = sprintf( __( 'Invalid shortcode name: %1$s. Do not use spaces or reserved characters: %2$s' ), $tag, '& / < > [ ] =' );
    101101                _doing_it_wrong( __FUNCTION__, $message, '4.4.0' );
     
    157157 *
    158158 * @since 3.6.0
    159159 *
    160  * @global array $shortcode_tags
    161  *
    162160 * @param string $content Content to search for shortcodes.
    163161 * @param string $tag     Shortcode tag to check.
    164162 * @return bool Whether the passed content contains the given shortcode.
     
    193191 *
    194192 * @since 2.5.0
    195193 *
    196  * @global array $shortcode_tags List of shortcode tags and their callback hooks.
    197  *
    198194 * @param string $content Content to search for shortcodes.
    199195 * @param bool $ignore_html When true, shortcodes inside HTML elements will be skipped.
    200196 * @return string Content with shortcodes filtered out.
    201197 */
    202198function do_shortcode( $content, $ignore_html = false ) {
    203         global $shortcode_tags;
    204199
    205         if ( false === strpos( $content, '[' ) ) {
    206                 return $content;
    207         }
    208 
    209         if (empty($shortcode_tags) || !is_array($shortcode_tags))
    210                 return $content;
    211 
    212200        // 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] );
     201        $tagnames = get_shortcode_tagnames( $content );
    215202
    216203        if ( empty( $tagnames ) ) {
    217204                return $content;
     
    246233 * @since 2.5.0
    247234 * @since 4.4.0 Added the `$tagnames` parameter.
    248235 *
    249  * @global array $shortcode_tags
    250  *
    251236 * @param array $tagnames Optional. List of shortcodes to find. Defaults to all registered shortcodes.
    252237 * @return string The shortcode search regular expression
    253238 */
    254239function get_shortcode_regex( $tagnames = null ) {
    255         global $shortcode_tags;
    256240
    257241        if ( empty( $tagnames ) ) {
    258                 $tagnames = array_keys( $shortcode_tags );
     242                $tagnames = get_shortcode_tagnames();
    259243        }
    260244        $tagregexp = join( '|', array_map('preg_quote', $tagnames) );
    261245
     
    265249                  '\\['                              // Opening bracket
    266250                . '(\\[?)'                           // 1: Optional second opening bracket for escaping shortcodes: [[tag]]
    267251                . "($tagregexp)"                     // 2: Shortcode name
    268                 . '(?![\\w-])'                       // Not followed by word character or hyphen
     252                . '(?=' . shortcode_name_terminators() . ')' // Followed by a shortcode name terminator.
    269253                . '('                                // 3: Unroll the loop: Inside the opening shortcode tag
    270254                .     '[^\\]\\/]*'                   // Not a closing bracket or forward slash
    271255                .     '(?:'
     
    293277}
    294278
    295279/**
     280 * Return regular expression of characters that terminate the name of a shortcode.
     281 * Dependent on charset of blog.
     282 *
     283 * @since x.x.x
     284 *
     285 * @return string Regular expression of terminating characters.
     286 */
     287function shortcode_name_terminators() {
     288        // Needs to be synced with get_shortcode_tagnames() expression below.
     289        if ( 'UTF-8' === _canonical_charset( get_option( 'blog_charset' ) ) ) {
     290                return '(?:[<>&\/\[\]\x00-\x20=]|\xc2\xa0)';
     291        }
     292        return '[<>&\/\[\]\x00-\x20=]';
     293}
     294
     295/**
     296 * Return registered shortcode tagnames, reduced to those in $text if given.
     297 *
     298 * @since x.x.x
     299 *
     300 * @global array $shortcode_tags
     301 *
     302 * @param string $text Optional. If given, only those registered shortcodes that appear in $text are returned.
     303 * @return array Array of shortcode names.
     304 */
     305function get_shortcode_tagnames( $text = null ) {
     306        global $shortcode_tags;
     307
     308        if ( ! $shortcode_tags || ! is_array( $shortcode_tags ) ) {
     309                return array();
     310        }
     311        if ( null === $text ) {
     312                return array_keys( $shortcode_tags );
     313        }
     314        if ( false === strpos( $text, '[' ) ) {
     315                return array();
     316        }
     317
     318        // Grouped expressions with unlimited repetition cause seg faults or match failures in PCRE <= 8.12 so use simple character class first and then post-process the matches.
     319        if ( ! preg_match_all( '/\[(?!\[)\/?\K[^<>&\/\[\]\x00-\x20=]+/', $text, $matches ) ) { // Needs to be synced with shortcode_name_terminators() expression above.
     320                return array();
     321        }
     322        if ( false !== strpos( $text, "\xc2\xa0" ) && 'UTF-8' === _canonical_charset( get_option( 'blog_charset' ) ) ) {
     323                $matches[0] = array_map( '_shortcode_name_terminator_chop_cb', $matches[0] );
     324        }
     325
     326        return array_values( array_intersect( array_keys( $shortcode_tags ), $matches[0] ) );
     327}
     328
     329/**
     330 * Callback for array_map in {@see get_shortcode_tagnames()} to chop off UTF-8 no-break space from potential shortcode name.
     331 *
     332 * @since x.x.x
     333 * @access private
     334 *
     335 * @param string $entry The potential shortcode name match.
     336 * @return string The potential shortcode name chopped down if necessary.
     337 */
     338function _shortcode_name_terminator_chop_cb( $entry ) {
     339        if ( false !== ( $pos = strpos( $entry, "\xc2\xa0" ) ) ) {
     340                $entry = substr( $entry, 0, $pos );
     341        }
     342        return $entry;
     343}
     344
     345/**
    296346 * Regular Expression callable for do_shortcode() for calling shortcode hook.
    297347 * @see get_shortcode_regex for details of the match array contents.
    298348 *
     
    589639 *
    590640 * @since 2.5.0
    591641 *
    592  * @global array $shortcode_tags
    593  *
    594642 * @param string $content Content to remove shortcode tags.
    595643 * @return string Content without shortcode tags.
    596644 */
    597645function strip_shortcodes( $content ) {
    598         global $shortcode_tags;
    599646
    600         if ( false === strpos( $content, '[' ) ) {
    601                 return $content;
    602         }
    603 
    604         if (empty($shortcode_tags) || !is_array($shortcode_tags))
    605                 return $content;
    606 
    607647        // Find all registered tag names in $content.
    608         preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $content, $matches );
     648        $tagnames = get_shortcode_tagnames( $content );
    609649
    610         $tags_to_remove = array_keys( $shortcode_tags );
    611 
    612650        /**
    613651         * Filters the list of shortcode tags to remove from the content.
    614652         *
    615653         * @since 4.7.0
    616654         *
    617          * @param array  $tag_array Array of shortcode tags to remove.
     655         * @param array  $tagnames Array of shortcode tags to remove.
    618656         * @param string $content   Content shortcodes are being removed from.
    619657         */
    620         $tags_to_remove = apply_filters( 'strip_shortcodes_tagnames', $tags_to_remove, $content );
     658        $tagnames = apply_filters( 'strip_shortcodes_tagnames', $tagnames, $content );
    621659
    622         $tagnames = array_intersect( $tags_to_remove, $matches[1] );
    623 
    624660        if ( empty( $tagnames ) ) {
    625661                return $content;
    626662        }
  • tests/phpunit/tests/shortcode.php

     
    332332        }
    333333
    334334        /**
     335         * @ticket 35022
     336         */
     337        function test_no_break_space_following_shortcode_tag() {
     338                do_shortcode( "[test-shortcode-tag\xC2\xA0foo=\"bar\"]" );
     339                $this->assertSame( 'test-shortcode-tag', $this->tagname );
     340                $this->assertSame( array( 'foo' => 'bar' ), $this->atts );
     341        }
     342
     343        /**
     344         * @ticket 35022
     345         */
     346        function test_non_no_break_space_following_shortcode_tag() {
     347                do_shortcode( "[test-shortcode-tag\xC2\xA1foo=\"bar\"]" );
     348                $this->assertNull( $this->tagname );
     349                $this->assertNull( $this->atts );
     350        }
     351
     352        function _charset_iso_8859_1() {
     353                return 'iso-8859-1';
     354        }
     355
     356        /**
     357         * @ticket 35022
     358         */
     359        function test_no_break_space_following_shortcode_tag_iso_8859_1() {
     360                add_filter( 'pre_option_blog_charset', array( $this, '_charset_iso_8859_1' ) );
     361
     362                do_shortcode( "[test-shortcode-tag foo=\"bar\"]" );
     363                $this->assertSame( 'test-shortcode-tag', $this->tagname );
     364                $this->assertSame( array( 'foo' => 'bar' ), $this->atts );
     365
     366                $this->atts = $this->content = $this->tagname = null;
     367                do_shortcode( "[test-shortcode-tag\xA0foo=\"bar\"]" ); // ISO-8859-1 no-break space not supported currently.
     368                $this->assertNull( $this->tagname );
     369                $this->assertNull( $this->atts );
     370
     371                $this->atts = $this->content = $this->tagname = null;
     372                do_shortcode( "[test-shortcode-tag\xC2\xA0foo=\"bar\"]" );
     373                $this->assertNull( $this->tagname );
     374                $this->assertNull( $this->atts );
     375
     376                remove_filter( 'pre_option_blog_charset', array( $this, '_charset_iso_8859_1' ) );
     377        }
     378
     379        /**
    335380         * @ticket 14050
    336381         */
    337382        function test_shortcode_unautop() {
     
    638683                                '',
    639684                                false,
    640685                        ),
     686                        array(
     687                                "bad\xc2\xa0space",
     688                                false,
     689                        ),
    641690                );
    642691        }
    643692
     
    655704                                'unreserved!#$%()*+,-.;?@^_{|}~chars',
    656705                                true,
    657706                        ),
     707                        array(
     708                                "unreservedutf8\xc2\xa1\xe0\xa0\x80\xf0\xa0\x80\x80\xc2\xa2chars",
     709                                true,
     710                        ),
    658711                );
    659712        }
    660713
  • tests/qunit/wp-includes/js/shortcode.js

     
    9696                equal( result, undefined, 'stub does not trigger match' );
    9797        });
    9898
     99        test( 'next() should find the shortcode delimited by no-break space (U+00A0)', function() {
     100                var result;
     101
     102
     103                result = wp.shortcode.next( 'foo', 'this has a no-break space delimited [foo\u00a0param="foo"] shortcode' );
     104                equal( result.index, 36, 'no-break space delimited foo shortcode found at index 36' );
     105        });
     106
    99107        test( 'replace() should replace the shortcode', function() {
    100108                var result;
    101109