Make WordPress Core

Ticket #50683: 50683.diff

File 50683.diff, 40.1 KB (added by johnbillion, 3 years ago)
  • src/wp-includes/class-wp-shortcode-parser.php

     
     1<?php
     2/**
     3 * Shortcode API: WP_Shortcode_Parser class
     4 *
     5 * @package WordPress
     6 * @since x.x.x
     7 */
     8
     9/**
     10 * A collection of methods for parsing and executing shortcodes in content.
     11 *
     12 * @since x.x.x
     13 */
     14class WP_Shortcode_Parser {
     15        /**
     16         * The content being parsed.
     17         *
     18         * @since x.x.x
     19         *
     20         * @var string $content
     21         */
     22        private $content;
     23
     24        /**
     25         * The current state of the parser.
     26         *
     27         * @since x.x.x
     28         *
     29         * @var int $state One of the `SHORTCODE_PARSE_STATE_*` constants listed below.
     30         */
     31        private $state;
     32
     33        /**
     34         * The current position of the parsing cursor in the content.
     35         *
     36         * @since x.x.x
     37         *
     38         * @var int $cursor_position
     39         */
     40        private $cursor_position;
     41
     42        /**
     43         * The stack of unprocessed shortcodes.
     44         *
     45         * As shortcodes are opened, they are placed on the stack, and as they're
     46         * closed, they're processed and removed.
     47         *
     48         * @since x.x.x
     49         *
     50         * @var array $stack
     51         */
     52        private $stack;
     53
     54        /**
     55         * A list of all registered shortcode tag names.
     56         *
     57         * @since x.x.x
     58         *
     59         * @var string[] $tagnames
     60         */
     61        private $tagnames;
     62
     63        /**
     64         * The shortcode currently being parsed.
     65         *
     66         * As a shortcode is being parsed, it is stored here. If a new shortcode is
     67         * found before parsing is complete, this shortcode is moved to the stack,
     68         * and the new shortcode is stored here.
     69         *
     70         * @since x.x.x
     71         *
     72         * @var array $current_shortcode {
     73         *     @type string $full_tag              The content that makes up this tag, from the opening
     74         *                                         bracket to the closing bracket.
     75         *     @type string $extra_opening_bracket Either the character '[' or an empty string,
     76         *                                         depending on whether there was an extra opening
     77         *                                         bracket for this shortcode. Essentially a flag.
     78         *     @type string $tag_slug              The shortcode tag slug.
     79         *     @type string $atts_and_values       The (unparsed) part of the shortcode tag that contains
     80         *                                         attributes and their optional values.
     81         *     @type string $self_closing_slash    Like $extra_opening_bracket, this is either the
     82         *                                         slash used to self-close the shortcode tag, or an
     83         *                                         empty string.
     84         *     @type int $cursor_position          The cursor position where this shortcode began.
     85         * }
     86         */
     87        private $current_shortcode;
     88
     89        /**
     90         * The default parsing state -- the cursor is not in a shortcode tag or
     91         * shortcode content or a quoted string in a shortcode attribute value.
     92         *
     93         * @since x.x.x
     94         *
     95         * @var int $SHORTCODE_PARSE_STATE_DEFAULT
     96         */
     97        const SHORTCODE_PARSE_STATE_DEFAULT = 0;
     98
     99        /**
     100         * The cursor is inside the shortcode tag, past the shortcode tag slug.
     101         *
     102         * @since x.x.x
     103         *
     104         * @var int $SHORTCODE_PARSE_STATE_IN_TAG
     105         */
     106        const SHORTCODE_PARSE_STATE_IN_TAG = 1;
     107
     108        /**
     109         * The cursor is in the content of the shortcode -- past the opening tag
     110         * but not yet to the closing tag.
     111         *
     112         * @since x.x.x
     113         *
     114         * @var int $SHORTCODE_PARSE_STATE_IN_CONTENT
     115         */
     116        const SHORTCODE_PARSE_STATE_IN_CONTENT = 2;
     117
     118        /**
     119         * The cursor is inside of a quoted string in the shortcode tag.
     120         *
     121         * @since x.x.x
     122         *
     123         * @var int $SHORTCODE_PARSE_STATE_IN_QUOTED_STRING
     124         */
     125        const SHORTCODE_PARSE_STATE_IN_QUOTED_STRING = 3;
     126
     127        /**
     128         * Store the content and tag names for later use.
     129         *
     130         * @since x.x.x
     131         *
     132         * @param string   $content The HTML content to parse for shortcodes.
     133         * @param string[] $tagnames An array of string shortcode tag names.
     134         */
     135        public function __construct( $content, $tagnames ) {
     136                $this->content  = $content;
     137                $this->tagnames = $tagnames;
     138        }
     139
     140        /**
     141         * Parse shortcodes in content and replace them with the output that their
     142         * handler functions generate.
     143         *
     144         * @since x.x.x
     145         *
     146         * @return string The content with shortcodes replaced by their output.
     147         */
     148        public function parse() {
     149                $this->stack = array();
     150
     151                /*
     152                 * A regular expression that checks whether a string appears to begin
     153                 * with a tag for a registered shortcode.
     154                 */
     155                $registered_shortcode_regex = '/^(?P<extra_opening_bracket>\\[?)(?P<opening_bracket>\\[)(?P<tag_slug>' . join( '|', array_map( 'preg_quote', $this->tagnames ) ) . ')(?![\\w-])/u';
     156
     157                $this->cursor_position = 0;
     158
     159                // Save some parsing time by starting a few characters before the first bracket.
     160                $this->forward_cursor_to_next_bracket();
     161
     162                $this->state = self::SHORTCODE_PARSE_STATE_DEFAULT;
     163
     164                $is_escaped = false;
     165                $delimiter  = null;
     166
     167                while ( $this->cursor_position < strlen( $this->content ) ) {
     168                        $char = substr( $this->content, $this->cursor_position, 1 );
     169
     170                        $found_escape_character = false;
     171
     172                        switch ( $this->state ) {
     173                                case self::SHORTCODE_PARSE_STATE_DEFAULT:
     174                                case self::SHORTCODE_PARSE_STATE_IN_CONTENT:
     175                                        if ( ! $is_escaped && '[' === $char && preg_match( $registered_shortcode_regex, substr( $this->content, $this->cursor_position ), $m ) ) {
     176                                                if ( $this->current_shortcode ) {
     177                                                        $this->stack[] = $this->current_shortcode;
     178                                                }
     179
     180                                                // We have found the beginning of a shortcode.
     181                                                $this->current_shortcode = array(
     182                                                        'full_tag'              => $m[0],
     183                                                        'extra_opening_bracket' => $m['extra_opening_bracket'],
     184                                                        'tag_slug'              => $m['tag_slug'],
     185                                                        'atts_and_values'       => '',
     186                                                        'self_closing_slash'    => '',
     187                                                        'inner_content'         => '',
     188                                                        'extra_closing_bracket' => '',
     189                                                        'cursor_position'       => $this->cursor_position,
     190                                                );
     191
     192                                                $this->cursor_position += strlen( $m[0] );
     193
     194                                                // Move back one so it's as if we just processed the last character of the shortcode slug.
     195                                                $this->cursor_position--;
     196
     197                                                $this->state = self::SHORTCODE_PARSE_STATE_IN_TAG;
     198                                        } elseif ( self::SHORTCODE_PARSE_STATE_IN_CONTENT === $this->state ) {
     199                                                $this->current_shortcode['full_tag'] .= $char;
     200
     201                                                if ( '[' === $char ) {
     202                                                        // Check whether it's a closing tag of any currently open shortcode.
     203                                                        $rest_of_closing_tag = '/' . $this->current_shortcode['tag_slug'] . ']';
     204
     205                                                        if ( substr( $this->content, $this->cursor_position + 1, strlen( $rest_of_closing_tag ) ) === $rest_of_closing_tag ) {
     206                                                                // The end of this shortcode.
     207
     208                                                                $this->current_shortcode['full_tag'] .= $rest_of_closing_tag;
     209
     210                                                                // Move the cursor to the end of the closing tag.
     211                                                                $this->cursor_position += strlen( $rest_of_closing_tag );
     212
     213                                                                if ( $this->current_shortcode['extra_opening_bracket'] ) {
     214                                                                        if ( ']' === substr( $this->content, $this->cursor_position + 1, 1 ) ) {
     215                                                                                $this->current_shortcode['full_tag']             .= ']';
     216                                                                                $this->current_shortcode['extra_closing_bracket'] = ']';
     217                                                                                $this->cursor_position++;
     218                                                                        } else {
     219                                                                                // If there was an extra opening bracket but not an extra closing bracket, ignore the extra opening bracket.
     220
     221                                                                                $this->current_shortcode['full_tag']              = substr( $this->current_shortcode['full_tag'], 1 );
     222                                                                                $this->current_shortcode['extra_opening_bracket'] = '';
     223
     224                                                                                // We initially thought it had an extra opening bracket, but it doesn't so it started one character later than we thought.
     225                                                                                $this->current_shortcode['cursor_position'] += 1;
     226                                                                        }
     227                                                                }
     228
     229                                                                $this->process_current_shortcode();
     230                                                        } else {
     231                                                                $found_matching_shortcode = false;
     232
     233                                                                for ( $stack_index = count( $this->stack ) - 1; $stack_index >= 0; $stack_index-- ) {
     234                                                                        $rest_of_closing_tag = '/' . $this->stack[ $stack_index ]['tag_slug'] . ']';
     235
     236                                                                        if ( substr( $this->content, $this->cursor_position + 1, strlen( $rest_of_closing_tag ) ) === $rest_of_closing_tag ) {
     237                                                                                // Yes, it closes this one.
     238                                                                                $found_matching_shortcode = true;
     239
     240                                                                                /*
     241                                                                                 * We already saved the bracket as part of the full tag, expecting that the
     242                                                                                 * closing tag would be for the current shortcode. It's not, so remove it.
     243                                                                                 */
     244                                                                                $this->current_shortcode['full_tag'] = substr( $this->current_shortcode['full_tag'], 0, -1 );
     245
     246                                                                                // This means that the "current" shortcode and any others above this one on the stack need to be closed out, because they are self-closing.
     247                                                                                do {
     248                                                                                        $this->current_shortcode['full_tag'] = substr( $this->current_shortcode['full_tag'], 0, -1 * strlen( $this->current_shortcode['inner_content'] ) );
     249
     250                                                                                        // And there is no inner content.
     251                                                                                        $this->current_shortcode['inner_content'] = '';
     252
     253                                                                                        $this->process_current_shortcode(); // This sets $current_shortcode using the top stack item, so we don't need to do it.
     254                                                                                } while ( count( $this->stack ) > $stack_index + 1 );
     255
     256                                                                                /*
     257                                                                                 * At this point, the shortcode that is being closed right now is $this->current_shortcode.
     258                                                                                 * The easiest way to process this without duplicating code is to reprocess the current
     259                                                                                 * character with the new stack and current shortcode, so the section above will get
     260                                                                                 * triggered, since the closing tag will be for the current shortcode.
     261                                                                                 */
     262
     263                                                                                continue 3;
     264                                                                        }
     265                                                                }
     266
     267                                                                if ( ! $found_matching_shortcode ) {
     268                                                                        $this->current_shortcode['inner_content'] .= $char;
     269                                                                }
     270                                                        }
     271                                                } else {
     272                                                        $this->current_shortcode['inner_content'] .= $char;
     273                                                }
     274                                        }
     275
     276                                        break;
     277                                case self::SHORTCODE_PARSE_STATE_IN_TAG:
     278                                        $this->current_shortcode['full_tag'] .= $char;
     279
     280                                        if ( '/' === $char && substr( $this->content, $this->cursor_position + 1, 1 ) === ']' ) {
     281                                                // The shortcode is over.
     282                                                $this->current_shortcode['self_closing_slash'] = '/';
     283                                                $this->current_shortcode['full_tag']          .= ']';
     284                                                $this->cursor_position++;
     285
     286                                                // If the shortcode had an extra opening bracket but doesn't have an extra closing bracket, ignore the extra opening bracket.
     287
     288                                                if ( $this->current_shortcode['extra_opening_bracket'] ) {
     289                                                        if ( ']' === substr( $this->content, $this->cursor_position + 1, 1 ) ) {
     290                                                                $this->current_shortcode['extra_closing_bracket'] = ']';
     291                                                                $this->current_shortcode['full_tag']             .= ']';
     292                                                                $this->cursor_position++;
     293                                                        } else {
     294                                                                $this->current_shortcode['full_tag']              = substr( $this->current_shortcode['full_tag'], 1 );
     295                                                                $this->current_shortcode['extra_opening_bracket'] = '';
     296
     297                                                                /*
     298                                                                 * We initially thought it had an extra opening bracket, but it doesn't,
     299                                                                 * so it started one character later than we thought.
     300                                                                 */
     301                                                                $this->current_shortcode['cursor_position'] += 1;
     302                                                        }
     303                                                }
     304
     305                                                $this->process_current_shortcode();
     306
     307                                                break;
     308                                        } elseif ( ']' === $char ) {
     309                                                if ( $this->current_shortcode['extra_opening_bracket'] ) {
     310                                                        /*
     311                                                         * This makes the assumption that this shortcode is closed as soon as the double brackets are found:
     312                                                         *
     313                                                         * [[my-shortcode]][/my-shortcode]]
     314                                                         *
     315                                                         * But in theory, this could just be a shortcode with the content "]".
     316                                                         */
     317
     318                                                        if ( ']' === substr( $this->content, $this->cursor_position + 1, 1 ) ) {
     319                                                                $this->current_shortcode['extra_closing_bracket'] = ']';
     320                                                                $this->current_shortcode['full_tag']             .= ']';
     321                                                                $this->cursor_position++;
     322
     323                                                                $this->process_current_shortcode();
     324                                                                break;
     325                                                        } else {
     326                                                                // There was not an extra closing bracket.
     327                                                        }
     328                                                }
     329
     330                                                if ( false === strpos( substr( $this->content, $this->cursor_position ), '[/' . $this->current_shortcode['tag_slug'] . ']' ) ) {
     331                                                        // If there's no closing tag, it's a self-enclosed shortcode, and we're done with it.
     332                                                        $this->process_current_shortcode();
     333                                                } else {
     334                                                        $this->state = self::SHORTCODE_PARSE_STATE_IN_CONTENT;
     335
     336                                                        $current_cursor_position = $this->cursor_position;
     337                                                        $this->forward_cursor_to_next_bracket();
     338
     339                                                        if ( $this->cursor_position !== $current_cursor_position ) {
     340                                                                // The +1 is because the character at $current_cursor_position has already been recorded.
     341                                                                $skipped_content = substr( $this->content, $current_cursor_position + 1, $this->cursor_position - $current_cursor_position );
     342
     343                                                                $this->current_shortcode['inner_content'] .= $skipped_content;
     344                                                                $this->current_shortcode['full_tag']      .= $skipped_content;
     345                                                        }
     346                                                }
     347                                        } else {
     348                                                $this->current_shortcode['atts_and_values'] .= $char;
     349
     350                                                if ( '"' === $char || "'" === $char ) {
     351                                                        $this->state = self::SHORTCODE_PARSE_STATE_IN_QUOTED_STRING;
     352                                                        $delimiter   = $char;
     353                                                } else {
     354                                                        // Nothing to do.
     355                                                }
     356                                        }
     357
     358                                        break;
     359                                case self::SHORTCODE_PARSE_STATE_IN_QUOTED_STRING:
     360                                        $this->current_shortcode['full_tag']        .= $char;
     361                                        $this->current_shortcode['atts_and_values'] .= $char;
     362
     363                                        if ( $is_escaped ) {
     364                                                // Nothing to do. This is just an escaped character to be taken literally.
     365                                        } else {
     366                                                // Not escaped.
     367                                                if ( '\\' === $char ) {
     368                                                        // The next character is escaped.
     369                                                        $found_escape_character = true;
     370                                                } elseif ( $char === $delimiter ) {
     371                                                        $this->state = self::SHORTCODE_PARSE_STATE_IN_TAG;
     372                                                        $delimiter   = null;
     373                                                }
     374                                        }
     375
     376                                        break;
     377                        }
     378
     379                        // Is the next character escaped?
     380                        if ( $found_escape_character ) {
     381                                $is_escaped = true;
     382                        } else {
     383                                // If we didn't find an escape character here, then no.
     384                                $is_escaped = false;
     385                        }
     386
     387                        $this->cursor_position++;
     388                }
     389
     390                if ( self::SHORTCODE_PARSE_STATE_IN_QUOTED_STRING === $this->state ) {
     391                        /*
     392                         * example: This is my content [footag foo=" [bartag]
     393                         * Should it be reprocessed in order to convert [bartag] or is this considered malformed?
     394                         */
     395                }
     396
     397                if ( self::SHORTCODE_PARSE_STATE_IN_TAG === $this->state ) {
     398                        /*
     399                         * example: This is my content [footag foo="abc" bar="def" [bartag]
     400                         * Should it be reprocessed in order to convert [bartag] or is this considered malformed?
     401                         */
     402                }
     403
     404                if ( $this->current_shortcode ) {
     405                        /*
     406                         * If we end with shortcodes still on the stack, then there was a situation like this:
     407                         *
     408                         * [footag] [bartag] [baztag] [footag]content[/footag]
     409                         *
     410                         * i.e., a scenario where the parser was unsure whether the first [footag] was self-closing or not.
     411                         *
     412                         * By this point, $content will be in this format:
     413                         *
     414                         * [footag] bartag-output baztag-output footag-content-output
     415                         *
     416                         * so we need to back up and process the still-stored shortcodes as unclosed.
     417                         *
     418                         * An extreme version of this would look like:
     419                         *
     420                         * [footag] [footag] [footag] [footag] [footag] [footag] [footag] [footag] ... [footag][/footag]
     421                         *
     422                         * where the last tag would be the only one processed normally above and there would be n-1 [footag]s still on the stack.
     423                         */
     424                        while ( $this->current_shortcode ) {
     425                                // What we thought was part of this tag was just regular content.
     426                                $this->current_shortcode['full_tag'] = substr( $this->current_shortcode['full_tag'], 0, -1 * strlen( $this->current_shortcode['inner_content'] ) );
     427
     428                                // And there is no inner content.
     429                                $this->current_shortcode['inner_content'] = '';
     430
     431                                $this->process_current_shortcode(); // This sets $current_shortcode, so we don't need to do it.
     432                        }
     433                }
     434
     435                return $this->content;
     436        }
     437
     438        /**
     439         * Create an argument to pass to do_shortcode_tag.
     440         *
     441         * The format of this argument was determined by the capture groups of the
     442         * regular expression that used to be used to parse shortcodes out of content.
     443         *
     444         * @since x.x.x
     445         *
     446         * @param array $shortcode An associative array comprising data about a shortcode in the text.
     447         * @return array A numerically-indexed array of the shortcode data ready for do_shortcode_tag().
     448         */
     449        private function shortcode_argument( $shortcode ) {
     450                return array(
     451                        $shortcode['full_tag'],
     452                        $shortcode['extra_opening_bracket'],
     453                        $shortcode['tag_slug'],
     454                        $shortcode['atts_and_values'],
     455                        $shortcode['self_closing_slash'],
     456                        $shortcode['inner_content'],
     457                        $shortcode['extra_closing_bracket'],
     458                );
     459        }
     460
     461        /**
     462         * Process the shortcode at the top of the stack.
     463         *
     464         * @since x.x.x
     465         *
     466         * The shortcode at the top of the stack is complete and can be processed.
     467         * Process it and modify the enclosing shortcode as if the content was passed in
     468         * with this shortcode already converted into HTML.
     469         */
     470        private function process_current_shortcode() {
     471                $argument_for_do_shortcode_tag = $this->shortcode_argument( $this->current_shortcode );
     472
     473                $shortcode_output = do_shortcode_tag( $argument_for_do_shortcode_tag );
     474
     475                /*
     476                 * Replace based on position rather than find and replace, since this content is possible:
     477                 *
     478                 * Test 123 [some-shortcode] To use my shortcode, type [[some-shortcode]].
     479                 */
     480                $this->content =
     481                        substr( $this->content, 0, $this->current_shortcode['cursor_position'] )
     482                        . $shortcode_output
     483                        . substr( $this->content, $this->current_shortcode['cursor_position'] + strlen( $this->current_shortcode['full_tag'] ) );
     484
     485                /*
     486                 * Update the cursor position to the end of this shortcode's output.
     487                 * The -1 is because the position is incremented after this gets called to move it to the next character.
     488                 */
     489                $this->cursor_position = $this->current_shortcode['cursor_position'] + strlen( $shortcode_output ) - 1;
     490
     491                // For any enclosing shortcode, its inner content needs to include the full output of this shortcode.
     492                if ( ! empty( $this->stack ) ) {
     493                        $this->current_shortcode = array_pop( $this->stack );
     494
     495                        $this->current_shortcode['inner_content'] .= $shortcode_output;
     496                        $this->current_shortcode['full_tag']      .= $shortcode_output;
     497
     498                        $this->state = self::SHORTCODE_PARSE_STATE_IN_CONTENT;
     499
     500                        $current_cursor_position = $this->cursor_position;
     501                        $this->forward_cursor_to_next_bracket();
     502
     503                        if ( $this->cursor_position !== $current_cursor_position ) {
     504                                /*
     505                                 * The +1 is because the character at $current_cursor_position has already been recorded.
     506                                 */
     507                                $skipped_content = substr( $this->content, $current_cursor_position + 1, $this->cursor_position - $current_cursor_position );
     508
     509                                $this->current_shortcode['inner_content'] .= $skipped_content;
     510                                $this->current_shortcode['full_tag']      .= $skipped_content;
     511                        }
     512                } else {
     513                        $this->current_shortcode = null;
     514
     515                        $this->state = self::SHORTCODE_PARSE_STATE_DEFAULT;
     516
     517                        // In the default state, we can skip over any content that couldn't be a shortcode, so let's move forward near the next bracket.
     518                        $this->forward_cursor_to_next_bracket();
     519                }
     520        }
     521
     522        /**
     523         * Moves the parsing cursor to the next possible location that might
     524         * include a shortcode.
     525         *
     526         * The specific location is directly before the next bracket or the end
     527         * of the content if there is no next bracket.
     528         *
     529         * @since x.x.x
     530         */
     531        private function forward_cursor_to_next_bracket() {
     532                /*
     533                 * The max() here is because $cursor_position can be -1 if a shortcode
     534                 * at the beginning of the content didn't have any output and reset the
     535                 * cursor back to the beginning. It's -1 instead of zero because it will
     536                 * be incremented later in the loop to set it to zero for the next iteration.
     537                 */
     538                $next_bracket_location = strpos( $this->content, '[', max( 0, $this->cursor_position ) );
     539
     540                if ( false === $next_bracket_location ) {
     541                        // There is no next bracket, so fast-forward to the end.
     542                        $next_bracket_location = strlen( $this->content );
     543                }
     544
     545                /*
     546                 * Again, the -1 is because this will be incremented before it is used,
     547                 * and we really want it to have a minimum value of zero.
     548                 */
     549                $this->cursor_position = max( -1, $next_bracket_location - 1 );
     550        }
     551}
  • src/wp-includes/shortcodes.php

    Property changes on: src/wp-includes/class-wp-shortcode-parser.php
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    224224
    225225        $content = do_shortcodes_in_html_tags( $content, $ignore_html, $tagnames );
    226226
    227         $pattern = get_shortcode_regex( $tagnames );
    228         $content = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $content );
     227        $shortcode_parser = new WP_Shortcode_Parser( $content, $tagnames );
     228        $content = $shortcode_parser->parse();
    229229
    230230        // Always restore square braces so we don't break things like <!--[if IE ]>.
    231231        $content = unescape_invalid_shortcodes( $content );
  • src/wp-settings.php

     
    223223require ABSPATH . WPINC . '/class-wp-tax-query.php';
    224224require ABSPATH . WPINC . '/update.php';
    225225require ABSPATH . WPINC . '/canonical.php';
     226require ABSPATH . WPINC . '/class-wp-shortcode-parser.php';
    226227require ABSPATH . WPINC . '/shortcodes.php';
    227228require ABSPATH . WPINC . '/embed.php';
    228229require ABSPATH . WPINC . '/class-wp-embed.php';
  • tests/phpunit/data/shortcodes/bracket_but_no_shortcode.txt

     
     1Square brackets can be used like this: [square brackets]
     2 No newline at end of file
  • tests/phpunit/data/shortcodes/closing_tag_and_content.txt

    Property changes on: tests/phpunit/data/shortcodes/bracket_but_no_shortcode.txt
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1[footag foo="this is the foo attribute"]this is the content[/footag]
     2 No newline at end of file
  • tests/phpunit/data/shortcodes/closing_tag_and_no_content.txt

    Property changes on: tests/phpunit/data/shortcodes/closing_tag_and_content.txt
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1[footag foo="this is the foo attribute"][/footag]
     2 No newline at end of file
  • tests/phpunit/data/shortcodes/flat_ten_across.txt

    Property changes on: tests/phpunit/data/shortcodes/closing_tag_and_no_content.txt
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1[footag]footag content[/footag]
     2[baztag]baztag content[/footag]
     3[bartag]bartag content[/footag]
     4[test-shortcode-tag]test-shortcode-tag content[/footag]
     5[dumptag att1="att1"]dumptag content[/footag]
     6[url]url content[/footag]
     7[hyphen]hyphen content[/footag]
     8[img att1="att1"]img content[/footag]
     9[hyphen-foo]hyphen-foo content[/footag]
     10[hyphen-foo-bar]hyphen-foo-bar content[/footag]
     11 No newline at end of file
  • tests/phpunit/data/shortcodes/long_content.txt

    Property changes on: tests/phpunit/data/shortcodes/flat_ten_across.txt
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1[footag]
     2I am the very model of a modern major general.
     3I am the very model of a modern major general.
     4I am the very model of a modern major general.
     5I am the very model of a modern major general.
     6I am the very model of a modern major general.
     7I am the very model of a modern major general.
     8I am the very model of a modern major general.
     9I am the very model of a modern major general.
     10I am the very model of a modern major general.
     11I am the very model of a modern major general.
     12I am the very model of a modern major general.
     13I am the very model of a modern major general.
     14I am the very model of a modern major general.
     15I am the very model of a modern major general.
     16I am the very model of a modern major general.
     17I am the very model of a modern major general.
     18I am the very model of a modern major general.
     19I am the very model of a modern major general.
     20I am the very model of a modern major general.
     21I am the very model of a modern major general.
     22I am the very model of a modern major general.
     23I am the very model of a modern major general.
     24I am the very model of a modern major general.
     25I am the very model of a modern major general.
     26I am the very model of a modern major general.
     27I am the very model of a modern major general.
     28I am the very model of a modern major general.
     29I am the very model of a modern major general.
     30I am the very model of a modern major general.
     31I am the very model of a modern major general.
     32I am the very model of a modern major general.
     33I am the very model of a modern major general.
     34I am the very model of a modern major general.
     35I am the very model of a modern major general.
     36I am the very model of a modern major general.
     37I am the very model of a modern major general.
     38I am the very model of a modern major general.
     39I am the very model of a modern major general.
     40I am the very model of a modern major general.
     41I am the very model of a modern major general.
     42I am the very model of a modern major general.
     43I am the very model of a modern major general.
     44I am the very model of a modern major general.
     45I am the very model of a modern major general.
     46I am the very model of a modern major general.
     47I am the very model of a modern major general.
     48I am the very model of a modern major general.
     49I am the very model of a modern major general.
     50I am the very model of a modern major general.
     51I am the very model of a modern major general.
     52I am the very model of a modern major general.
     53I am the very model of a modern major general.
     54I am the very model of a modern major general.
     55I am the very model of a modern major general.
     56I am the very model of a modern major general.
     57I am the very model of a modern major general.
     58I am the very model of a modern major general.
     59I am the very model of a modern major general.
     60I am the very model of a modern major general.
     61I am the very model of a modern major general.
     62I am the very model of a modern major general.
     63I am the very model of a modern major general.
     64I am the very model of a modern major general.
     65I am the very model of a modern major general.
     66I am the very model of a modern major general.
     67I am the very model of a modern major general.
     68I am the very model of a modern major general.
     69I am the very model of a modern major general.
     70I am the very model of a modern major general.
     71I am the very model of a modern major general.
     72I am the very model of a modern major general.
     73I am the very model of a modern major general.
     74I am the very model of a modern major general.
     75I am the very model of a modern major general.
     76I am the very model of a modern major general.
     77I am the very model of a modern major general.
     78I am the very model of a modern major general.
     79I am the very model of a modern major general.
     80I am the very model of a modern major general.
     81I am the very model of a modern major general.
     82I am the very model of a modern major general.
     83[/footag]
     84 No newline at end of file
  • tests/phpunit/data/shortcodes/many_attributes.txt

    Property changes on: tests/phpunit/data/shortcodes/long_content.txt
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1[footag attr1="This is attr1." attr2="This is attr2" attr3="This is attr3" attr4="This is attr4" attr5="This is attr5" attr6="This is attr6" attr7="This is attr7" attr8="This is attr8" attr9="This is attr9" attr10="This is attr10"]
     2 No newline at end of file
  • tests/phpunit/data/shortcodes/nested_ten_deep.txt

    Property changes on: tests/phpunit/data/shortcodes/many_attributes.txt
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1[footag]
     2        [baztag]
     3                [bartag]
     4                        [test-shortcode-tag]
     5                                [dumptag att1="att1"]
     6                                        [url]
     7                                                [hyphen]
     8                                                        [img att1="att1"]
     9                                                                [hyphen-foo]
     10                                                                        [hyphen-foo-bar]
     11                                                                        [/hyphen-foo-bar]
     12                                                                [/hyphen-foo]
     13                                                        [/img]
     14                                                [/hyphen]
     15                                        [/url]
     16                                [/dumptag]
     17                        [/test-shortcode-tag]
     18                [/bartag]
     19        [/baztag]
     20[/footag]
     21 No newline at end of file
  • tests/phpunit/data/shortcodes/nested_two_deep.txt

    Property changes on: tests/phpunit/data/shortcodes/nested_ten_deep.txt
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1[footag foo="This is the foo attribute"]
     2        [baztag foo="This is the baztag foo attribute." bar="This is the baztag bar attribute"]
     3                This is the baztag content.
     4        [/baztag]
     5[/footag]
     6 No newline at end of file
  • tests/phpunit/data/shortcodes/no_closing_tag.txt

    Property changes on: tests/phpunit/data/shortcodes/nested_two_deep.txt
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1[footag foo="this is the foo attribute"]
     2 No newline at end of file
  • tests/phpunit/data/shortcodes/not_actually_a_shortcode.txt

    Property changes on: tests/phpunit/data/shortcodes/no_closing_tag.txt
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1[footag This is not a shortcode because there is no closing bracket.
     2 No newline at end of file
  • tests/phpunit/data/shortcodes/positional_attribute.txt

    Property changes on: tests/phpunit/data/shortcodes/not_actually_a_shortcode.txt
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1[test-shortcode-tag http://www.youtube.com/watch?v=eBGIQ7ZuuiU]
     2 No newline at end of file
  • tests/phpunit/data/shortcodes/positional_attributes.txt

    Property changes on: tests/phpunit/data/shortcodes/positional_attribute.txt
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1[test-shortcode-tag 123 https://wordpress.org/ 0 "foo" bar]
     2 No newline at end of file
  • tests/phpunit/data/shortcodes/self_closing_tag.txt

    Property changes on: tests/phpunit/data/shortcodes/positional_attributes.txt
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1[footag foo="this is the foo attribute" /]
     2 No newline at end of file
  • tests/phpunit/data/shortcodes/self_closing_tag_no_space.txt

    Property changes on: tests/phpunit/data/shortcodes/self_closing_tag.txt
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1[footag foo="this is the foo attribute"/]
     2 No newline at end of file
  • tests/phpunit/tests/shortcode.php

    Property changes on: tests/phpunit/data/shortcodes/self_closing_tag_no_space.txt
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    990990                );
    991991                $this->assertSame( 'test-shortcode-tag', $this->tagname );
    992992        }
     993
     994        function test_bracket_in_shortcode_attribute() {
     995                do_shortcode( '[test-shortcode-tag subject="[This is my subject]" /]' );
     996                $expected_attrs = array(
     997                        'subject' => '[This is my subject]',
     998                );
     999                $this->assertEquals( $expected_attrs, $this->atts );
     1000        }
     1001
     1002        function test_self_closing_shortcode_with_quoted_end_tag() {
     1003                $out = do_shortcode( '[test-shortcode-tag]Test 123[footag foo="[/test-shortcode-tag]"/] [baztag]bazcontent[/baztag]' );
     1004
     1005                $this->assertEquals( 'Test 123foo = [/test-shortcode-tag] content = bazcontent', $out );
     1006        }
     1007
     1008        function test_nested_shortcodes() {
     1009                do_shortcode( '[test-shortcode-tag]Some content [footag foo="foo content"/] some other content[/test-shortcode-tag]' );
     1010
     1011                $this->assertEquals( 'Some content foo = foo content some other content', $this->content );
     1012
     1013                $out = do_shortcode( '[footag foo="1"][footag foo="2"][footag foo="3"][footag foo="4"][/footag][footag foo="4a"][/footag][/footag][/footag][/footag]' );
     1014
     1015                $this->assertEquals( 'foo = 1', $out );
     1016
     1017                $out = do_shortcode( '[footag foo="1"] abc [bartag foo="2"] def [/footag] something else [test-shortcode-tag attr="[/footag]" attr2="[/bartag]"][/test-shortcode-tag]' );
     1018
     1019                $this->assertEquals( 'foo = 1something else ', $out );
     1020        }
     1021
     1022        /**
     1023         * @ticket 49955
     1024         */
     1025        function test_ticket_49955() {
     1026                add_shortcode(
     1027                        'ucase',
     1028                        function( $atts, $content ) {
     1029                                return strtoupper( $content );
     1030                        }
     1031                );
     1032
     1033                $out = do_shortcode( 'This [[ucase]] shortcode [ucase]demonstrates[/ucase] the usage of enclosing shortcodes.' );
     1034
     1035                $this->assertEquals( 'This [ucase] shortcode DEMONSTRATES the usage of enclosing shortcodes.', $out );
     1036        }
     1037
     1038        /**
     1039         * @ticket 43725
     1040         */
     1041        public function test_same_tag_multiple_formats_open_closed_one() {
     1042                $in = <<<EOT
     1043This post uses URL multiple times.
     1044
     1045[url]Now this is wrapped[/url]
     1046
     1047[url] This one is standalone
     1048
     1049[url]Now this is wrapped too[/url]
     1050EOT;
     1051
     1052                $expected = <<<EOT
     1053This post uses URL multiple times.
     1054
     1055http://www.wordpress.org/
     1056
     1057http://www.wordpress.org/ This one is standalone
     1058
     1059http://www.wordpress.org/
     1060EOT;
     1061
     1062                $out = do_shortcode( $in );
     1063                $this->assertEquals( strip_ws( $expected ), strip_ws( $out ) );
     1064        }
     1065
     1066        /**
     1067         * @ticket 43725
     1068         */
     1069        public function test_same_tag_multiple_formats_open_closed_two() {
     1070                $in = <<<EOT
     1071This post uses URL multiple times.
     1072
     1073[url]Now this is wrapped[/url]
     1074
     1075[url/] This one is standalone
     1076
     1077[url]Now this is wrapped too[/url]
     1078EOT;
     1079
     1080                $expected = <<<EOT
     1081This post uses URL multiple times.
     1082
     1083http://www.wordpress.org/
     1084
     1085http://www.wordpress.org/ This one is standalone
     1086
     1087http://www.wordpress.org/
     1088EOT;
     1089
     1090                $out = do_shortcode( $in );
     1091                $this->assertEquals( strip_ws( $expected ), strip_ws( $out ) );
     1092        }
     1093
     1094        /**
     1095         * Not really a test suite, but the easiest way I could find to test the speed of shortcode parsing.
     1096         */
     1097        public function test_speed() {
     1098                $total_time = 0;
     1099
     1100                // Load a list of example shortcodes.
     1101                $example_shortcodes = glob( './tests/phpunit/data/shortcodes/*.txt' );
     1102
     1103                $alphabet = "`1234567890-=qwertyuiop\asdfghjkl;'zxcvbnm,./~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:\"ZXCVBNM<>?          \n\n\n\n\n\n\n\n\t\t\t\t\t\t\t\t";
     1104
     1105                // Add random strings to all of the example shortcodes.
     1106                // This lets us test speed on content that is different lengths, and it also
     1107                // works as a fuzzer, letting us find any situation where parsing fails because of the content.
     1108
     1109                $short_content  = '';
     1110                $medium_content = '';
     1111                $long_content   = '';
     1112
     1113                for ( $i = 0; $i < 10; $i++ ) {
     1114                        $short_content .= $alphabet[ rand( 0, strlen( $alphabet ) - 1 ) ];
     1115                }
     1116
     1117                for ( $i = 0; $i < 100; $i++ ) {
     1118                        $medium_content .= $alphabet[ rand( 0, strlen( $alphabet ) - 1 ) ];
     1119                }
     1120
     1121                for ( $i = 0; $i < 1000; $i++ ) {
     1122                        $long_content .= $alphabet[ rand( 0, strlen( $alphabet ) - 1 ) ];
     1123                }
     1124
     1125                foreach ( $example_shortcodes as $example_shortcode ) {
     1126                        $shortcode = file_get_contents( $example_shortcode );
     1127
     1128                        $variations = array(
     1129                                'shortcode only'                   => $shortcode,
     1130                                'short content suffix'             => $short_content . $shortcode,
     1131                                'medium content suffix'            => $medium_content . $shortcode,
     1132                                'long content suffix'              => $long_content . $shortcode,
     1133                                'short content prefix'             => $shortcode . $short_content,
     1134                                'medium content prefix'            => $shortcode . $medium_content,
     1135                                'long content prefix'              => $shortcode . $long_content,
     1136                                'short content prefix and suffix'  => $shortcode . $short_content . $shortcode,
     1137                                'medium content prefix and suffix' => $shortcode . $medium_content . $shortcode,
     1138                                'long content prefix and suffix'   => $shortcode . $long_content . $shortcode,
     1139                                'short content inner'              => mb_substr( $short_content, 0, round( mb_strlen( $short_content ) ) ) . $shortcode . mb_substr( $short_content, round( mb_strlen( $short_content ) / 2 ) ),
     1140                                'medium content inner'             => mb_substr( $medium_content, 0, round( mb_strlen( $medium_content ) ) ) . $shortcode . mb_substr( $medium_content, round( mb_strlen( $medium_content ) / 2 ) ),
     1141                                'long content inner'               => mb_substr( $long_content, 0, round( mb_strlen( $long_content ) ) ) . $shortcode . mb_substr( $long_content, round( mb_strlen( $long_content ) / 2 ) ),
     1142                        );
     1143
     1144                        foreach ( $variations as $variation => $content ) {
     1145                                $variation_key = basename( $example_shortcode ) . ':' . $variation;
     1146                                error_log( 'Running variation ' . $variation_key );
     1147
     1148                                $start = microtime( true );
     1149
     1150                                do_shortcode( $content );
     1151
     1152                                $end = microtime( true );
     1153
     1154                                $total_time += ( $end - $start );
     1155
     1156                                error_log( $variation_key . ': ' . ( $end - $start ) . " seconds\n" );
     1157                        }
     1158                }
     1159
     1160                error_log( "\nTotal time: " . $total_time . " seconds\n" );
     1161        }
    9931162}