WordPress 3.3.2-scripts
[autoinstalls/wordpress.git] / wp-includes / formatting.php
index 6c53f8631455252fb7c80193c38c3adaf7362f2c..d43e848fe4d89a3b29eacf85f5c4138d4966b591 100644 (file)
  */
 function wptexturize($text) {
        global $wp_cockneyreplace;
-       static $static_setup = false, $opening_quote, $closing_quote, $default_no_texturize_tags, $default_no_texturize_shortcodes, $static_characters, $static_replacements, $dynamic_characters, $dynamic_replacements;
-       $output = '';
-       $curl = '';
-       $textarr = preg_split('/(<.*>|\[.*\])/Us', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
-       $stop = count($textarr);
+       static $opening_quote, $closing_quote, $en_dash, $em_dash, $default_no_texturize_tags, $default_no_texturize_shortcodes, $static_characters, $static_replacements, $dynamic_characters, $dynamic_replacements;
 
-       // No need to set up these variables more than once
-       if (!$static_setup) {
+       // No need to set up these static variables more than once
+       if ( empty( $opening_quote ) ) {
                /* translators: opening curly quote */
                $opening_quote = _x('&#8220;', 'opening curly quote');
                /* translators: closing curly quote */
                $closing_quote = _x('&#8221;', 'closing curly quote');
+               /* translators: en dash */
+               $en_dash = _x('&#8211;', 'en dash');
+               /* translators: em dash */
+               $em_dash = _x('&#8212;', 'em dash');
 
                $default_no_texturize_tags = array('pre', 'code', 'kbd', 'style', 'script', 'tt');
                $default_no_texturize_shortcodes = array('code');
@@ -53,13 +53,11 @@ function wptexturize($text) {
                        $cockneyreplace = array("&#8217;tain&#8217;t","&#8217;twere","&#8217;twas","&#8217;tis","&#8217;twill","&#8217;til","&#8217;bout","&#8217;nuff","&#8217;round","&#8217;cause");
                }
 
-               $static_characters = array_merge(array('---', ' -- ', '--', ' - ', 'xn&#8211;', '...', '``', '\'\'', ' (tm)'), $cockney);
-               $static_replacements = array_merge(array('&#8212;', ' &#8212; ', '&#8211;', ' &#8211; ', 'xn--', '&#8230;', $opening_quote, $closing_quote, ' &#8482;'), $cockneyreplace);
+               $static_characters = array_merge( array('---', ' -- ', '--', ' - ', 'xn&#8211;', '...', '``', '\'\'', ' (tm)'), $cockney );
+               $static_replacements = array_merge( array($em_dash, ' ' . $em_dash . ' ', $en_dash, ' ' . $en_dash . ' ', 'xn--', '&#8230;', $opening_quote, $closing_quote, ' &#8482;'), $cockneyreplace );
 
                $dynamic_characters = array('/\'(\d\d(?:&#8217;|\')?s)/', '/\'(\d)/', '/(\s|\A|[([{<]|")\'/', '/(\d)"/', '/(\d)\'/', '/(\S)\'([^\'\s])/', '/(\s|\A|[([{<])"(?!\s)/', '/"(\s|\S|\Z)/', '/\'([\s.]|\Z)/', '/\b(\d+)x(\d+)\b/');
                $dynamic_replacements = array('&#8217;$1','&#8217;$1', '$1&#8216;', '$1&#8243;', '$1&#8242;', '$1&#8217;$2', '$1' . $opening_quote . '$2', $closing_quote . '$1', '&#8217;$1', '$1&#215;$2');
-
-               $static_setup = true;
        }
 
        // Transform into regexp sub-expression used in _wptexturize_pushpop_element
@@ -70,32 +68,27 @@ function wptexturize($text) {
        $no_texturize_tags_stack = array();
        $no_texturize_shortcodes_stack = array();
 
-       for ( $i = 0; $i < $stop; $i++ ) {
-               $curl = $textarr[$i];
+       $textarr = preg_split('/(<.*>|\[.*\])/Us', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
+
+       foreach ( $textarr as &$curl ) {
+               if ( empty( $curl ) )
+                       continue;
 
-               if ( !empty($curl) && '<' != $curl[0] && '[' != $curl[0]
-                               && empty($no_texturize_shortcodes_stack) && empty($no_texturize_tags_stack)) {
-                       // This is not a tag, nor is the texturization disabled
-                       // static strings
+               // Only call _wptexturize_pushpop_element if first char is correct tag opening
+               $first = $curl[0];
+               if ( '<' === $first ) {
+                       _wptexturize_pushpop_element($curl, $no_texturize_tags_stack, $no_texturize_tags, '<', '>');
+               } elseif ( '[' === $first ) {
+                       _wptexturize_pushpop_element($curl, $no_texturize_shortcodes_stack, $no_texturize_shortcodes, '[', ']');
+               } elseif ( empty($no_texturize_shortcodes_stack) && empty($no_texturize_tags_stack) ) {
+                       // This is not a tag, nor is the texturization disabled static strings
                        $curl = str_replace($static_characters, $static_replacements, $curl);
                        // regular expressions
                        $curl = preg_replace($dynamic_characters, $dynamic_replacements, $curl);
-               } elseif (!empty($curl)) {
-                       /*
-                        * Only call _wptexturize_pushpop_element if first char is correct
-                        * tag opening
-                        */
-                       if ('<' == $curl[0])
-                               _wptexturize_pushpop_element($curl, $no_texturize_tags_stack, $no_texturize_tags, '<', '>');
-                       elseif ('[' == $curl[0])
-                               _wptexturize_pushpop_element($curl, $no_texturize_shortcodes_stack, $no_texturize_shortcodes, '[', ']');
                }
-
                $curl = preg_replace('/&([^#])(?![a-zA-Z1-4]{1,8};)/', '&#038;$1', $curl);
-               $output .= $curl;
        }
-
-       return $output;
+       return implode( '', $textarr );
 }
 
 /**
@@ -243,16 +236,48 @@ function _autop_newline_preservation_helper( $matches ) {
  * @param string $pee The content.
  * @return string The filtered content.
  */
-function shortcode_unautop($pee) {
+function shortcode_unautop( $pee ) {
        global $shortcode_tags;
 
-       if ( !empty($shortcode_tags) && is_array($shortcode_tags) ) {
-               $tagnames = array_keys($shortcode_tags);
-               $tagregexp = join( '|', array_map('preg_quote', $tagnames) );
-               $pee = preg_replace('/<p>\\s*?(\\[(' . $tagregexp . ')\\b.*?\\/?\\](?:.+?\\[\\/\\2\\])?)\\s*<\\/p>/s', '$1', $pee);
-       }
-
-       return $pee;
+       if ( empty( $shortcode_tags ) || !is_array( $shortcode_tags ) ) {
+               return $pee;
+       }
+
+       $tagregexp = join( '|', array_map( 'preg_quote', array_keys( $shortcode_tags ) ) );
+
+       $pattern =
+                 '/'
+               . '<p>'                              // Opening paragraph
+               . '\\s*+'                            // Optional leading whitespace
+               . '('                                // 1: The shortcode
+               .     '\\['                          // Opening bracket
+               .     "($tagregexp)"                 // 2: Shortcode name
+               .     '\\b'                          // Word boundary
+                                                    // Unroll the loop: Inside the opening shortcode tag
+               .     '[^\\]\\/]*'                   // Not a closing bracket or forward slash
+               .     '(?:'
+               .         '\\/(?!\\])'               // A forward slash not followed by a closing bracket
+               .         '[^\\]\\/]*'               // Not a closing bracket or forward slash
+               .     ')*?'
+               .     '(?:'
+               .         '\\/\\]'                   // Self closing tag and closing bracket
+               .     '|'
+               .         '\\]'                      // Closing bracket
+               .         '(?:'                      // Unroll the loop: Optionally, anything between the opening and closing shortcode tags
+               .             '[^\\[]*+'             // Not an opening bracket
+               .             '(?:'
+               .                 '\\[(?!\\/\\2\\])' // An opening bracket not followed by the closing shortcode tag
+               .                 '[^\\[]*+'         // Not an opening bracket
+               .             ')*+'
+               .             '\\[\\/\\2\\]'         // Closing shortcode tag
+               .         ')?'
+               .     ')'
+               . ')'
+               . '\\s*+'                            // optional trailing whitespace
+               . '<\\/p>'                           // closing paragraph
+               . '/s';
+
+       return preg_replace( $pattern, '$1', $pee );
 }
 
 /**
@@ -305,34 +330,31 @@ function seems_utf8($str) {
 function _wp_specialchars( $string, $quote_style = ENT_NOQUOTES, $charset = false, $double_encode = false ) {
        $string = (string) $string;
 
-       if ( 0 === strlen( $string ) ) {
+       if ( 0 === strlen( $string ) )
                return '';
-       }
 
        // Don't bother if there are no specialchars - saves some processing
-       if ( !preg_match( '/[&<>"\']/', $string ) ) {
+       if ( ! preg_match( '/[&<>"\']/', $string ) )
                return $string;
-       }
 
        // Account for the previous behaviour of the function when the $quote_style is not an accepted value
-       if ( empty( $quote_style ) ) {
+       if ( empty( $quote_style ) )
                $quote_style = ENT_NOQUOTES;
-       } elseif ( !in_array( $quote_style, array( 0, 2, 3, 'single', 'double' ), true ) ) {
+       elseif ( ! in_array( $quote_style, array( 0, 2, 3, 'single', 'double' ), true ) )
                $quote_style = ENT_QUOTES;
-       }
 
        // Store the site charset as a static to avoid multiple calls to wp_load_alloptions()
-       if ( !$charset ) {
+       if ( ! $charset ) {
                static $_charset;
-               if ( !isset( $_charset ) ) {
+               if ( ! isset( $_charset ) ) {
                        $alloptions = wp_load_alloptions();
                        $_charset = isset( $alloptions['blog_charset'] ) ? $alloptions['blog_charset'] : '';
                }
                $charset = $_charset;
        }
-       if ( in_array( $charset, array( 'utf8', 'utf-8', 'UTF8' ) ) ) {
+
+       if ( in_array( $charset, array( 'utf8', 'utf-8', 'UTF8' ) ) )
                $charset = 'UTF-8';
-       }
 
        $_quote_style = $quote_style;
 
@@ -344,28 +366,27 @@ function _wp_specialchars( $string, $quote_style = ENT_NOQUOTES, $charset = fals
        }
 
        // Handle double encoding ourselves
-       if ( !$double_encode ) {
+       if ( $double_encode ) {
+               $string = @htmlspecialchars( $string, $quote_style, $charset );
+       } else {
+               // Decode &amp; into &
                $string = wp_specialchars_decode( $string, $_quote_style );
 
-               /* Critical */
-               // The previous line decodes &amp;phrase; into &phrase;  We must guarantee that &phrase; is valid before proceeding.
-               $string = wp_kses_normalize_entities($string);
+               // Guarantee every &entity; is valid or re-encode the &
+               $string = wp_kses_normalize_entities( $string );
 
-               // Now proceed with custom double-encoding silliness
-               $string = preg_replace( '/&(#?x?[0-9a-z]+);/i', '|wp_entity|$1|/wp_entity|', $string );
-       }
+               // Now re-encode everything except &entity;
+               $string = preg_split( '/(&#?x?[0-9a-z]+;)/i', $string, -1, PREG_SPLIT_DELIM_CAPTURE );
 
-       $string = @htmlspecialchars( $string, $quote_style, $charset );
+               for ( $i = 0; $i < count( $string ); $i += 2 )
+                       $string[$i] = @htmlspecialchars( $string[$i], $quote_style, $charset );
 
-       // Handle double encoding ourselves
-       if ( !$double_encode ) {
-               $string = str_replace( array( '|wp_entity|', '|/wp_entity|' ), array( '&', ';' ), $string );
+               $string = implode( '', $string );
        }
 
        // Backwards compatibility
-       if ( 'single' === $_quote_style ) {
+       if ( 'single' === $_quote_style )
                $string = str_replace( "'", '&#039;', $string );
-       }
 
        return $string;
 }
@@ -547,6 +568,7 @@ function remove_accents($string) {
        if (seems_utf8($string)) {
                $chars = array(
                // Decompositions for Latin-1 Supplement
+               chr(194).chr(170) => 'a', chr(194).chr(186) => 'o',
                chr(195).chr(128) => 'A', chr(195).chr(129) => 'A',
                chr(195).chr(130) => 'A', chr(195).chr(131) => 'A',
                chr(195).chr(132) => 'A', chr(195).chr(133) => 'A',
@@ -573,11 +595,11 @@ function remove_accents($string) {
                chr(195).chr(176) => 'd', chr(195).chr(177) => 'n',
                chr(195).chr(178) => 'o', chr(195).chr(179) => 'o',
                chr(195).chr(180) => 'o', chr(195).chr(181) => 'o',
-               chr(195).chr(182) => 'o', chr(195).chr(182) => 'o',
+               chr(195).chr(182) => 'o', chr(195).chr(184) => 'o',
                chr(195).chr(185) => 'u', chr(195).chr(186) => 'u',
                chr(195).chr(187) => 'u', chr(195).chr(188) => 'u',
                chr(195).chr(189) => 'y', chr(195).chr(190) => 'th',
-               chr(195).chr(191) => 'y',
+               chr(195).chr(191) => 'y', chr(195).chr(152) => 'O',
                // Decompositions for Latin Extended-A
                chr(196).chr(128) => 'A', chr(196).chr(129) => 'a',
                chr(196).chr(130) => 'A', chr(196).chr(131) => 'a',
@@ -818,7 +840,7 @@ function sanitize_title_for_query($title) {
 }
 
 /**
- * Sanitizes title, replacing whitespace with dashes.
+ * Sanitizes title, replacing whitespace and a few other characters with dashes.
  *
  * Limits the output to alphanumeric characters, underscore (_) and dash (-).
  * Whitespace becomes a dash.
@@ -826,9 +848,11 @@ function sanitize_title_for_query($title) {
  * @since 1.2.0
  *
  * @param string $title The title to be sanitized.
+ * @param string $raw_title Optional. Not used.
+ * @param string $context Optional. The operation for which the string is sanitized.
  * @return string The sanitized title.
  */
-function sanitize_title_with_dashes($title) {
+function sanitize_title_with_dashes($title, $raw_title = '', $context = 'display') {
        $title = strip_tags($title);
        // Preserve escaped octets.
        $title = preg_replace('|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title);
@@ -847,6 +871,20 @@ function sanitize_title_with_dashes($title) {
        $title = strtolower($title);
        $title = preg_replace('/&.+?;/', '', $title); // kill entities
        $title = str_replace('.', '-', $title);
+
+       if ( 'save' == $context ) {
+               // nbsp, ndash and mdash
+               $title = str_replace( array( '%c2%a0', '%e2%80%93', '%e2%80%94' ), '-', $title );
+               // iexcl and iquest
+               $title = str_replace( array( '%c2%a1', '%c2%bf' ), '', $title );
+               // angle quotes
+               $title = str_replace( array( '%c2%ab', '%c2%bb', '%e2%80%b9', '%e2%80%ba' ), '', $title );
+               // curly quotes
+               $title = str_replace( array( '%e2%80%98', '%e2%80%99', '%e2%80%9c', '%e2%80%9d' ), '', $title );
+               // copy, reg, deg, hellip and trade
+               $title = str_replace( array( '%c2%a9', '%c2%ae', '%c2%b0', '%e2%80%a6', '%e2%84%a2' ), '', $title );
+       }
+
        $title = preg_replace('/[^%a-z0-9 _-]/', '', $title);
        $title = preg_replace('/\s+/', '-', $title);
        $title = preg_replace('|-+|', '-', $title);
@@ -876,7 +914,7 @@ function sanitize_sql_orderby( $orderby ){
 /**
  * Santizes a html classname to ensure it only contains valid characters
  *
- * Strips the string down to A-Z,a-z,0-9,'-' if this results in an empty
+ * Strips the string down to A-Z,a-z,0-9,_,-. If this results in an empty
  * string then it will return the alternative value supplied.
  *
  * @todo Expand to support the full range of CDATA that a class attribute can contain.
@@ -890,10 +928,10 @@ function sanitize_sql_orderby( $orderby ){
  */
 function sanitize_html_class( $class, $fallback = '' ) {
        //Strip out any % encoded octets
-       $sanitized = preg_replace('|%[a-fA-F0-9][a-fA-F0-9]|', '', $class);
+       $sanitized = preg_replace( '|%[a-fA-F0-9][a-fA-F0-9]|', '', $class );
 
-       //Limit to A-Z,a-z,0-9,'-'
-       $sanitized = preg_replace('/[^A-Za-z0-9-]/', '', $sanitized);
+       //Limit to A-Z,a-z,0-9,_,-
+       $sanitized = preg_replace( '/[^A-Za-z0-9_-]/', '', $sanitized );
 
        if ( '' == $sanitized )
                $sanitized = $fallback;
@@ -1013,8 +1051,8 @@ function force_balance_tags( $text ) {
        $stacksize = 0;
        $tagqueue = '';
        $newtext = '';
-       $single_tags = array('br', 'hr', 'img', 'input'); // Known single-entity/self-closing tags
-       $nestable_tags = array('blockquote', 'div', 'span'); // Tags that can be immediately nested within themselves
+       $single_tags = array( 'br', 'hr', 'img', 'input' ); // Known single-entity/self-closing tags
+       $nestable_tags = array( 'blockquote', 'div', 'span', 'q' ); // Tags that can be immediately nested within themselves
 
        // WP bug fix for comments - in case you REALLY meant to type '< !--'
        $text = str_replace('< !--', '<    !--', $text);
@@ -1147,7 +1185,7 @@ function format_to_post($content) {
  * Add leading zeros when necessary.
  *
  * If you set the threshold to '4' and the number is '10', then you will get
- * back '0010'. If you set the number to '4' and the number is '5000', then you
+ * back '0010'. If you set the threshold to '4' and the number is '5000', then you
  * will get back '5000'.
  *
  * Uses sprintf to append the amount of zeros based on the $threshold parameter
@@ -1238,7 +1276,7 @@ function addslashes_gpc($gpc) {
  *
  * @since 2.0.0
  *
- * @param array|string $value The array or string to be striped.
+ * @param array|string $value The array or string to be stripped.
  * @return array|string Stripped array (or string in the callback).
  */
 function stripslashes_deep($value) {
@@ -1312,9 +1350,17 @@ function antispambot($emailaddy, $mailto=0) {
  */
 function _make_url_clickable_cb($matches) {
        $url = $matches[2];
-       $suffix = '';
 
-       /** Include parentheses in the URL only if paired **/
+       if ( ')' == $matches[3] && strpos( $url, '(' ) ) {
+               // If the trailing character is a closing parethesis, and the URL has an opening parenthesis in it, add the closing parenthesis to the URL.
+               // Then we can let the parenthesis balancer do its thing below.
+               $url .= $matches[3];
+               $suffix = '';
+       } else {
+               $suffix = $matches[3];
+       }
+
+       // Include parentheses in the URL only if paired
        while ( substr_count( $url, '(' ) < substr_count( $url, ')' ) ) {
                $suffix = strrchr( $url, ')' ) . $suffix;
                $url = substr( $url, 0, strrpos( $url, ')' ) );
@@ -1380,23 +1426,117 @@ function _make_email_clickable_cb($matches) {
  *
  * @since 0.71
  *
- * @param string $ret Content to convert URIs.
+ * @param string $text Content to convert URIs.
  * @return string Content with converted URIs.
  */
-function make_clickable($ret) {
-       $ret = ' ' . $ret;
-       // in testing, using arrays here was found to be faster
-       $save = @ini_set('pcre.recursion_limit', 10000);
-       $retval = preg_replace_callback('#(?<!=[\'"])(?<=[*\')+.,;:!&$\s>])(\()?([\w]+?://(?:[\w\\x80-\\xff\#%~/?@\[\]-]{1,2000}|[\'*(+.,;:!=&$](?![\b\)]|(\))?([\s]|$))|(?(1)\)(?![\s<.,;:]|$)|\)))+)#is', '_make_url_clickable_cb', $ret);
-       if (null !== $retval )
-               $ret = $retval;
-       @ini_set('pcre.recursion_limit', $save);
-       $ret = preg_replace_callback('#([\s>])((www|ftp)\.[\w\\x80-\\xff\#$%&~/.\-;:=,?@\[\]+]+)#is', '_make_web_ftp_clickable_cb', $ret);
-       $ret = preg_replace_callback('#([\s>])([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})#i', '_make_email_clickable_cb', $ret);
-       // this one is not in an array because we need it to run last, for cleanup of accidental links within links
-       $ret = preg_replace("#(<a( [^>]+?>|>))<a [^>]+?>([^>]+?)</a></a>#i", "$1$3</a>", $ret);
-       $ret = trim($ret);
-       return $ret;
+function make_clickable( $text ) {
+       $r = '';
+       $textarr = preg_split( '/(<[^<>]+>)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); // split out HTML tags
+       foreach ( $textarr as $piece ) {
+               if ( empty( $piece ) || ( $piece[0] == '<' && ! preg_match('|^<\s*[\w]{1,20}+://|', $piece) ) ) {
+                       $r .= $piece;
+                       continue;
+               }
+
+               // Long strings might contain expensive edge cases ...
+               if ( 10000 < strlen( $piece ) ) {
+                       // ... break it up
+                       foreach ( _split_str_by_whitespace( $piece, 2100 ) as $chunk ) { // 2100: Extra room for scheme and leading and trailing paretheses
+                               if ( 2101 < strlen( $chunk ) ) {
+                                       $r .= $chunk; // Too big, no whitespace: bail.
+                               } else {
+                                       $r .= make_clickable( $chunk );
+                               }
+                       }
+               } else {
+                       $ret = " $piece "; // Pad with whitespace to simplify the regexes
+
+                       $url_clickable = '~
+                               ([\\s(<.,;:!?])                                        # 1: Leading whitespace, or punctuation
+                               (                                                      # 2: URL
+                                       [\\w]{1,20}+://                                # Scheme and hier-part prefix
+                                       (?=\S{1,2000}\s)                               # Limit to URLs less than about 2000 characters long
+                                       [\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]*+         # Non-punctuation URL character
+                                       (?:                                            # Unroll the Loop: Only allow puctuation URL character if followed by a non-punctuation URL character
+                                               [\'.,;:!?)]                            # Punctuation URL character
+                                               [\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]++ # Non-punctuation URL character
+                                       )*
+                               )
+                               (\)?)                                                  # 3: Trailing closing parenthesis (for parethesis balancing post processing)
+                       ~xS'; // The regex is a non-anchored pattern and does not have a single fixed starting character.
+                             // Tell PCRE to spend more time optimizing since, when used on a page load, it will probably be used several times.
+
+                       $ret = preg_replace_callback( $url_clickable, '_make_url_clickable_cb', $ret );
+
+                       $ret = preg_replace_callback( '#([\s>])((www|ftp)\.[\w\\x80-\\xff\#$%&~/.\-;:=,?@\[\]+]+)#is', '_make_web_ftp_clickable_cb', $ret );
+                       $ret = preg_replace_callback( '#([\s>])([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})#i', '_make_email_clickable_cb', $ret );
+
+                       $ret = substr( $ret, 1, -1 ); // Remove our whitespace padding.
+                       $r .= $ret;
+               }
+       }
+
+       // Cleanup of accidental links within links
+       $r = preg_replace( '#(<a( [^>]+?>|>))<a [^>]+?>([^>]+?)</a></a>#i', "$1$3</a>", $r );
+       return $r;
+}
+
+/**
+ * Breaks a string into chunks by splitting at whitespace characters.
+ * The length of each returned chunk is as close to the specified length goal as possible,
+ * with the caveat that each chunk includes its trailing delimiter.
+ * Chunks longer than the goal are guaranteed to not have any inner whitespace.
+ *
+ * Joining the returned chunks with empty delimiters reconstructs the input string losslessly.
+ *
+ * Input string must have no null characters (or eventual transformations on output chunks must not care about null characters)
+ *
+ * <code>
+ * _split_str_by_whitespace( "1234 67890 1234 67890a cd 1234   890 123456789 1234567890a    45678   1 3 5 7 90 ", 10 ) ==
+ * array (
+ *   0 => '1234 67890 ',  // 11 characters: Perfect split
+ *   1 => '1234 ',        //  5 characters: '1234 67890a' was too long
+ *   2 => '67890a cd ',   // 10 characters: '67890a cd 1234' was too long
+ *   3 => '1234   890 ',  // 11 characters: Perfect split
+ *   4 => '123456789 ',   // 10 characters: '123456789 1234567890a' was too long
+ *   5 => '1234567890a ', // 12 characters: Too long, but no inner whitespace on which to split
+ *   6 => '   45678   ',  // 11 characters: Perfect split
+ *   7 => '1 3 5 7 9',    //  9 characters: End of $string
+ * );
+ * </code>
+ *
+ * @since 3.4.0
+ * @access private
+ *
+ * @param string $string The string to split
+ * @param    int $goal   The desired chunk length.
+ * @return array Numeric array of chunks.
+ */
+function _split_str_by_whitespace( $string, $goal ) {
+       $chunks = array();
+
+       $string_nullspace = strtr( $string, "\r\n\t\v\f ", "\000\000\000\000\000\000" );
+
+       while ( $goal < strlen( $string_nullspace ) ) {
+               $pos = strrpos( substr( $string_nullspace, 0, $goal + 1 ), "\000" );
+
+               if ( false === $pos ) {
+                       $pos = strpos( $string_nullspace, "\000", $goal + 1 );
+                       if ( false === $pos ) {
+                               break;
+                       }
+               }
+
+               $chunks[] = substr( $string, 0, $pos + 1 );
+               $string = substr( $string, $pos + 1 );
+               $string_nullspace = substr( $string_nullspace, $pos + 1 );
+       }
+
+       if ( $string ) {
+               $chunks[] = $string;
+       }
+
+       return $chunks;
 }
 
 /**
@@ -1599,8 +1739,8 @@ function _wp_iso_convert( $match ) {
  *
  * Requires and returns a date in the Y-m-d H:i:s format. Simply subtracts the
  * value of the 'gmt_offset' option. Return format can be overridden using the
- * $format parameter. If PHP5 is supported, the function uses the DateTime and
- * DateTimeZone objects to respect time zone differences in DST.
+ * $format parameter. The DateTime and DateTimeZone classes are used to respect
+ * time zone differences in DST.
  *
  * @since 1.2.0
  *
@@ -1612,8 +1752,7 @@ function _wp_iso_convert( $match ) {
 function get_gmt_from_date($string, $format = 'Y-m-d H:i:s') {
        preg_match('#([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2}) ([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})#', $string, $matches);
        $tz = get_option('timezone_string');
-       if( class_exists('DateTime') && $tz ) {
-               //PHP5
+       if ( $tz ) {
                date_default_timezone_set( $tz );
                $datetime = new DateTime( $string );
                $datetime->setTimezone( new DateTimeZone('UTC') );
@@ -1622,9 +1761,7 @@ function get_gmt_from_date($string, $format = 'Y-m-d H:i:s') {
                $string_gmt = gmdate($format, $datetime->format('U'));
 
                date_default_timezone_set('UTC');
-       }
-       else {
-               //PHP4
+       } else {
                $string_time = gmmktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
                $string_gmt = gmdate($format, $string_time - get_option('gmt_offset') * 3600);
        }
@@ -1853,10 +1990,10 @@ function human_time_diff( $from, $to = '' ) {
  *
  * @since 1.5.0
  *
- * @param string $text The excerpt. If set to empty an excerpt is generated.
+ * @param string $text Optional. The excerpt. If set to empty, an excerpt is generated.
  * @return string The excerpt.
  */
-function wp_trim_excerpt($text) {
+function wp_trim_excerpt($text = '') {
        $raw_excerpt = $text;
        if ( '' == $text ) {
                $text = get_the_content('');
@@ -1865,21 +2002,39 @@ function wp_trim_excerpt($text) {
 
                $text = apply_filters('the_content', $text);
                $text = str_replace(']]>', ']]&gt;', $text);
-               $text = strip_tags($text);
                $excerpt_length = apply_filters('excerpt_length', 55);
                $excerpt_more = apply_filters('excerpt_more', ' ' . '[...]');
-               $words = preg_split("/[\n\r\t ]+/", $text, $excerpt_length + 1, PREG_SPLIT_NO_EMPTY);
-               if ( count($words) > $excerpt_length ) {
-                       array_pop($words);
-                       $text = implode(' ', $words);
-                       $text = $text . $excerpt_more;
-               } else {
-                       $text = implode(' ', $words);
-               }
+               $text = wp_trim_words( $text, $excerpt_length, $excerpt_more );
        }
        return apply_filters('wp_trim_excerpt', $text, $raw_excerpt);
 }
 
+/**
+ * Trims text to a certain number of words.
+ *
+ * @since 3.3.0
+ *
+ * @param string $text Text to trim.
+ * @param int $num_words Number of words. Default 55.
+ * @param string $more What to append if $text needs to be trimmed. Default '&hellip;'.
+ * @return string Trimmed text.
+ */
+function wp_trim_words( $text, $num_words = 55, $more = null ) {
+       if ( null === $more )
+               $more = __( '&hellip;' );
+       $original_text = $text;
+       $text = wp_strip_all_tags( $text );
+       $words_array = preg_split( "/[\n\r\t ]+/", $text, $num_words + 1, PREG_SPLIT_NO_EMPTY );
+       if ( count( $words_array ) > $num_words ) {
+               array_pop( $words_array );
+               $text = implode( ' ', $words_array );
+               $text = $text . $more;
+       } else {
+               $text = implode( ' ', $words_array );
+       }
+       return apply_filters( 'wp_trim_words', $text, $num_words, $more, $original_text );
+}
+
 /**
  * Converts named entities into numbered entities.
  *
@@ -1889,6 +2044,12 @@ function wp_trim_excerpt($text) {
  * @return string Text with converted entities.
  */
 function ent2ncr($text) {
+
+       // Allow a plugin to short-circuit and override the mappings.
+       $filtered = apply_filters( 'pre_ent2ncr', null, $text );
+       if( null !== $filtered )
+               return $filtered;
+
        $to_ncr = array(
                '&quot;' => '&#34;',
                '&amp;' => '&#38;',
@@ -2240,7 +2401,7 @@ function esc_sql( $sql ) {
  * Checks and cleans a URL.
  *
  * A number of characters are removed from the URL. If the URL is for displaying
- * (the default behaviour) amperstands are also replaced. The 'clean_url' filter
+ * (the default behaviour) ampersands are also replaced. The 'clean_url' filter
  * is applied to the returned cleaned URL.
  *
  * @since 2.8.0
@@ -2249,7 +2410,7 @@ function esc_sql( $sql ) {
  *
  * @param string $url The URL to be cleaned.
  * @param array $protocols Optional. An array of acceptable protocols.
- *             Defaults to 'http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet' if not set.
+ *             Defaults to 'http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet', 'mms', 'rtsp', 'svn' if not set.
  * @param string $_context Private. Use esc_url_raw() for database usage.
  * @return string The cleaned $url after the 'clean_url' filter is applied.
  */
@@ -2264,10 +2425,10 @@ function esc_url( $url, $protocols = null, $_context = 'display' ) {
        $url = str_replace(';//', '://', $url);
        /* If the URL doesn't appear to contain a scheme, we
         * presume it needs http:// appended (unless a relative
-        * link starting with / or a php file).
+        * link starting with /, # or ? or a php file).
         */
-       if ( strpos($url, ':') === false &&
-               substr( $url, 0, 1 ) != '/' && substr( $url, 0, 1 ) != '#' && !preg_match('/^[a-z0-9-]+?\.php/i', $url) )
+       if ( strpos($url, ':') === false && ! in_array( $url[0], array( '/', '#', '?' ) ) &&
+               preg_match('/^[a-z0-9-]+?\.php/i', $url) )
                $url = 'http://' . $url;
 
        // Replace ampersands and single quotes only when displaying.
@@ -2277,8 +2438,8 @@ function esc_url( $url, $protocols = null, $_context = 'display' ) {
                $url = str_replace( "'", '&#039;', $url );
        }
 
-       if ( !is_array($protocols) )
-               $protocols = array ('http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet', 'mms', 'rtsp', 'svn');
+       if ( ! is_array( $protocols ) )
+               $protocols = wp_allowed_protocols();
        if ( wp_kses_bad_protocol( $url, $protocols ) != $url )
                return '';
 
@@ -2386,7 +2547,7 @@ function esc_textarea( $text ) {
  * @return string
  */
 function tag_escape($tag_name) {
-       $safe_tag = strtolower( preg_replace('/[^a-zA-Z_:]/', '', $tag_name) );
+       $safe_tag = strtolower( preg_replace('/[^a-zA-Z0-9_:]/', '', $tag_name) );
        return apply_filters('tag_escape', $safe_tag, $tag_name);
 }
 
@@ -2441,6 +2602,15 @@ function sanitize_option($option, $value) {
                        }
                        break;
 
+               case 'new_admin_email':
+                       $value = sanitize_email($value);
+                       if ( !is_email($value) ) {
+                               $value = get_option( $option ); // Resets option to stored value in the case of failed sanitization
+                               if ( function_exists('add_settings_error') )
+                                       add_settings_error('new_admin_email', 'invalid_admin_email', __('The email address entered did not appear to be a valid email address. Please enter a valid email address.'));
+                       }
+                       break;
+
                case 'thumbnail_size_w':
                case 'thumbnail_size_h':
                case 'medium_size_w':
@@ -2535,11 +2705,31 @@ function sanitize_option($option, $value) {
                        }
                        break;
 
-               default :
-                       $value = apply_filters("sanitize_option_{$option}", $value, $option);
+               case 'WPLANG':
+                       $allowed = get_available_languages();
+                       if ( ! in_array( $value, $allowed ) && ! empty( $value ) )
+                               $value = get_option( $option );
+                       break;
+
+               case 'timezone_string':
+                       $allowed_zones = timezone_identifiers_list();
+                       if ( ! in_array( $value, $allowed_zones ) && ! empty( $value ) ) {
+                               $value = get_option( $option ); // Resets option to stored value in the case of failed sanitization
+                               if ( function_exists('add_settings_error') )
+                                       add_settings_error('timezone_string', 'invalid_timezone_string', __('The timezone you have entered is not valid. Please select a valid timezone.') );
+                       }
+                       break;
+
+               case 'permalink_structure':
+               case 'category_base':
+               case 'tag_base':
+                       $value = esc_url_raw( $value );
+                       $value = str_replace( 'http://', '', $value );
                        break;
        }
 
+       $value = apply_filters("sanitize_option_{$option}", $value, $option);
+
        return $value;
 }
 
@@ -2678,7 +2868,7 @@ function wp_sprintf_l($pattern, $args) {
 
        // Translate and filter the delimiter set (avoid ampersands and entities here)
        $l = apply_filters('wp_sprintf_l', array(
-               /* translators: used between list items, there is a space after the coma */
+               /* translators: used between list items, there is a space after the comma */
                'between'          => __(', '),
                /* translators: used between list items, there is a space after the and */
                'between_last_two' => __(', and '),
@@ -2769,7 +2959,7 @@ function _links_add_base($m) {
  * This function by default only applies to <a> tags, however this can be
  * modified by the 3rd param.
  *
- * <b>NOTE:</b> Any current target attributed will be striped and replaced.
+ * <b>NOTE:</b> Any current target attributed will be stripped and replaced.
  *
  * @since 2.7.0
  *
@@ -2912,7 +3102,7 @@ function capital_P_dangit( $text ) {
  * @return string Sanitized mime type
  */
 function sanitize_mime_type( $mime_type ) {
-       $sani_mime_type = preg_replace( '/[^-*.a-zA-Z0-9\/]/', '', $mime_type );
+       $sani_mime_type = preg_replace( '/[^-+*.a-zA-Z0-9\/]/', '', $mime_type );
        return apply_filters( 'sanitize_mime_type', $sani_mime_type, $mime_type );
 }