WordPress.org

Make WordPress Core

Ticket #18312: 18312.diff

File 18312.diff, 8.1 KB (added by kurtpayne, 7 years ago)

New version of force_balance_tags function

  • wp-includes/formatting.php

     
    981981 * @since 2.0.4
    982982 *
    983983 * @author Leonard Lin <leonard@acm.org>
     984 * @author Kurt Payne <kpayne@godaddy.com>
    984985 * @license GPL
    985986 * @copyright November 4, 2001
    986987 * @version 1.1
    987988 * @todo Make better - change loop condition to $text in 1.2
    988989 * @internal Modified by Scott Reilly (coffee2code) 02 Aug 2004
     990 *              1.2  Updated to fix bug 18312 - improper balancing of nested lists
     991 *                       Added the ability to skip cleaning of certain tags     
    989992 *              1.1  Fixed handling of append/stack pop order of end text
    990993 *                       Added Cleaning Hooks
    991994 *              1.0  First Version
     
    994997 * @return string Balanced text.
    995998 */
    996999function force_balance_tags( $text ) {
    997         $tagstack = array();
    998         $stacksize = 0;
    999         $tagqueue = '';
    1000         $newtext = '';
    10011000        $single_tags = array('br', 'hr', 'img', 'input'); // Known single-entity/self-closing tags
    1002         $nestable_tags = array('blockquote', 'div', 'span'); // Tags that can be immediately nested within themselves
     1001        $skip_tags = array('script', 'style'); // No fixing should be done inside these tags
    10031002
    10041003        // WP bug fix for comments - in case you REALLY meant to type '< !--'
    10051004        $text = str_replace('< !--', '<    !--', $text);
    10061005        // WP bug fix for LOVE <3 (and other situations with '<' before a number)
    10071006        $text = preg_replace('#<([0-9]{1})#', '&lt;$1', $text);
    10081007
    1009         while ( preg_match("/<(\/?[\w:]*)\s*([^>]*)>/", $text, $regex) ) {
    1010                 $newtext .= $tagqueue;
     1008        // Copy the old text to a new field
     1009        $newtext = $text;
    10111010
    1012                 $i = strpos($text, $regex[0]);
    1013                 $l = strlen($regex[0]);
     1011        // Keeping track of the tags we've seen
     1012        $stack = array();
    10141013
    1015                 // clear the shifter
    1016                 $tagqueue = '';
    1017                 // Pop or Push
    1018                 if ( isset($regex[1][0]) && '/' == $regex[1][0] ) { // End Tag
    1019                         $tag = strtolower(substr($regex[1],1));
    1020                         // if too many closing tags
    1021                         if( $stacksize <= 0 ) {
    1022                                 $tag = '';
    1023                                 // or close to be safe $tag = '/' . $tag;
    1024                         }
    1025                         // if stacktop value = tag close value then pop
    1026                         else if ( $tagstack[$stacksize - 1] == $tag ) { // found closing tag
    1027                                 $tag = '</' . $tag . '>'; // Close Tag
    1028                                 // Pop
    1029                                 array_pop( $tagstack );
    1030                                 $stacksize--;
    1031                         } else { // closing tag not at top, search for it
    1032                                 for ( $j = $stacksize-1; $j >= 0; $j-- ) {
    1033                                         if ( $tagstack[$j] == $tag ) {
    1034                                         // add tag to tagqueue
    1035                                                 for ( $k = $stacksize-1; $k >= $j; $k--) {
    1036                                                         $tagqueue .= '</' . array_pop( $tagstack ) . '>';
    1037                                                         $stacksize--;
    1038                                                 }
    1039                                                 break;
    1040                                         }
    1041                                 }
    1042                                 $tag = '';
    1043                         }
    1044                 } else { // Begin Tag
    1045                         $tag = strtolower($regex[1]);
     1014        // Start the position tracking for the new text at 0
     1015        $newpos = 0;
    10461016
    1047                         // Tag Cleaning
     1017        // Start off with a skip level of 0.  Skipping tags can be nested and we
     1018        // have to roll / unroll each one every time we open / close a skip tag
     1019        $skip_level = 0;
    10481020
    1049                         // If self-closing or '', don't do anything.
    1050                         if ( substr($regex[2],-1) == '/' || $tag == '' ) {
    1051                                 // do nothing
    1052                         }
    1053                         // ElseIf it's a known single-entity tag but it doesn't close itself, do so
    1054                         elseif ( in_array($tag, $single_tags) ) {
    1055                                 $regex[2] .= '/';
    1056                         } else {        // Push the tag onto the stack
    1057                                 // If the top of the stack is the same as the tag we want to push, close previous tag
    1058                                 if ( $stacksize > 0 && !in_array($tag, $nestable_tags) && $tagstack[$stacksize - 1] == $tag ) {
    1059                                         $tagqueue = '</' . array_pop ($tagstack) . '>';
    1060                                         $stacksize--;
    1061                                 }
    1062                                 $stacksize = array_push ($tagstack, $tag);
    1063                         }
     1021        // Look for matches
     1022        while ( preg_match('/<(\/?[\w:]*)\s*([^>]*)>/S', $text, $matches, PREG_OFFSET_CAPTURE, $pos) ) {       
    10641023
    1065                         // Attributes
    1066                         $attributes = $regex[2];
    1067                         if( !empty($attributes) )
    1068                                 $attributes = ' '.$attributes;
     1024                /**
     1025                 * Matches:
     1026                 *  [0][0] = whole match (e.g. <div style="align:center;">
     1027                 *  [0][1] = numeric position in $text (e.g. 0)
     1028                 *  [1][0] = just the tag (e.g. div or /div)
     1029                 *  [1][1] = numeric position in $text (e.g. 1)
     1030                 *  [2][0] = any attributes (e.g. style="align:center;")
     1031                 *  [2][1] = numeric position in $text (e.g. 5)
     1032                 */
    10691033
    1070                         $tag = '<' . $tag . $attributes . '>';
    1071                         //If already queuing a close tag, then put this tag on, too
    1072                         if ( !empty($tagqueue) ) {
    1073                                 $tagqueue .= $tag;
    1074                                 $tag = '';
    1075                         }
     1034                // Is it a closing tag or not?
     1035                $closing = ($matches[1][0][0] == '/');
     1036
     1037                // Just the tag, no brackets, slashes, or attributes
     1038                $tag = ( $closing ? strtolower(substr($matches[1][0], 1) ) : strtolower($matches[1][0]));
     1039
     1040                // Find the position of this
     1041                $newpos = strpos($newtext, $matches[0][0], $newpos);
     1042       
     1043                // Skip any empty tags (most likely comments or malformed html tags ...
     1044                // we could make the doc worse by adding a closing tag to a bad html tag)
     1045                if ( empty($matches[1][0]) ) {
     1046                        $pos = $matches[0][1] + 1;
    10761047                }
    1077                 $newtext .= substr($text, 0, $i) . $tag;
    1078                 $text = substr($text, $i + $l);
    1079         }
     1048
     1049                // If it's a known single tag and it's a closing tag, discard it
     1050                elseif ( in_array($tag, $single_tags) && $closing ) {           
     1051                        if ( $skip_level <= 0 )
     1052                                $newtext = substr($newtext, 0, $newpos) . substr($newtext, $newpos + strlen($matches[0][0]));
     1053
     1054                        // Move to the next match
     1055                        $pos = $matches[0][1] + 1;
     1056                }
     1057
     1058                // If it's a known single tag and it's not properly closed, close it
     1059                elseif ( in_array($tag, $single_tags) && '/>' != substr($matches[0][0], -2) ) {
     1060
     1061                        // Add the properly closed tag to the new text
     1062                        if ( $skip_level <= 0 )
     1063                                $newtext = substr($newtext, 0, $newpos) . substr($matches[0][0], 0, -1) . '/>' . substr($newtext, $newpos + strlen($matches[0][0]));
     1064
     1065                        // Move to the next match
     1066                        $pos = $matches[0][1] + 1;
     1067                }
     1068
     1069                // If it's an opening tag, add it to the stack
     1070                elseif ( !$closing ) {
     1071
     1072                        // If it's a self closing tag (regardless of whether we have it in our list) we don't
     1073                        // want it in the stack, as it's legitimate XML, but it will mess up our balancing act
     1074                        if ( '/>' != substr($matches[0][0], -2) && ($skip_level <= 0 || in_array($tag, $skip_tags)) )
     1075                                array_push($stack, $matches);
     1076
     1077                        // Are we in skip mode?
     1078                        if ( in_array($tag, $skip_tags) )
     1079                                $skip_level++;
     1080
     1081                        // Move to the next match
     1082                        $pos = $matches[0][1] + 1;
     1083                }
     1084       
     1085        // If we're here, it must be a closing tag
     1086        else {
    10801087
    1081         // Clear Tag Queue
    1082         $newtext .= $tagqueue;
     1088                        // If the stack is empty, the closing tag must be useless, skip it, move to the next match
     1089                        if ( empty($stack) ) {
     1090                                if ( $skip_level <= 0 )
     1091                                        $newtext = substr($newtext, 0, $newpos) . substr($newtext, $newpos + strlen($matches[0][0]));
     1092                                $pos = $matches[0][1] + 1;
     1093                        } else {
     1094                                $lasttag = array_pop($stack);
    10831095
    1084         // Add Remaining text
    1085         $newtext .= $text;
     1096                                // If the closing tag matches the last tag on the stack, just move to the next match
     1097                                if ( strtolower($lasttag[1][0]) == $tag ) {
     1098                                        $pos = $matches[0][1] + 1;
    10861099
    1087         // Empty Stack
    1088         while( $x = array_pop($tagstack) )
    1089                 $newtext .= '</' . $x . '>'; // Add remaining tags to close
     1100                                // Can we stop skipping?
     1101                                if ( $skip_level > 0 && in_array($tag, $skip_tags) )
     1102                                        $skip_level--;
     1103
     1104                                // If it doesn't match, add in a closing tag, but don't remove it from the original text.  This will force the tag to run through the
     1105                                // loop again and continue to go up a level in the stack until it finds its match or until the stack is empty and the tag is discarded
     1106                                } else {
     1107                                        if ( $skip_level <= 0 )
     1108                                                $newtext = substr($newtext, 0, $newpos) . '</' . $lasttag[1][0] . '>' . substr($newtext, $newpos);
     1109                                }
     1110                        }
     1111                }
     1112    }
     1113
     1114        // If there are still elements on the stack, add the closing tags at the bottom of the text
     1115        while ( !empty($stack) ) {
     1116                $tag = array_pop($stack);
     1117                $newtext .= '</' . $tag[1][0] . '>';
     1118        }
    10901119
    10911120        // WP fix for the bug with HTML comments
    1092         $newtext = str_replace("< !--","<!--",$newtext);
    1093         $newtext = str_replace("<    !--","< !--",$newtext);
     1121        $newtext = str_replace('< !--', '<!--', $newtext);
     1122        $newtext = str_replace('<    !--', '< !--', $newtext);
    10941123
    10951124        return $newtext;
    10961125}