Make WordPress Core

Ticket #50683: shortcode-parser.8.diff

File shortcode-parser.8.diff, 40.1 KB (added by cfinke, 5 years ago)

Renamed Shortcode_Parser class to WP_Shortcode_Parser and moved it to its own file; added "@since 5.7.0" to all of the docblocks; moved shortcode examples to the phpunit/data/ folder.

  • src/wp-includes/class-wp-shortcode-parser.php

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

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