X-Git-Url: https://scripts.mit.edu/gitweb/autoinstalls/wordpress.git/blobdiff_plain/11be8dc178e77d0b46189bbd8e33a216a9b90942..refs/tags/wordpress-2.9.1:/wp-includes/formatting.php diff --git a/wp-includes/formatting.php b/wp-includes/formatting.php index 53df8084..dc9ed2f6 100644 --- a/wp-includes/formatting.php +++ b/wp-includes/formatting.php @@ -1,6 +1,6 @@ |\[.*\])/Us', $text, -1, PREG_SPLIT_DELIM_CAPTURE); $stop = count($textarr); + + // No need to setup these variables more than once + if (!$static_setup) { + /* translators: opening curly quote */ + $opening_quote = _x('“', 'opening curly quote'); + /* translators: closing curly quote */ + $closing_quote = _x('”', 'closing curly quote'); + + $default_no_texturize_tags = array('pre', 'code', 'kbd', 'style', 'script', 'tt'); + $default_no_texturize_shortcodes = array('code'); + + // if a plugin has provided an autocorrect array, use it + if ( isset($wp_cockneyreplace) ) { + $cockney = array_keys($wp_cockneyreplace); + $cockneyreplace = array_values($wp_cockneyreplace); + } else { + $cockney = array("'tain't","'twere","'twas","'tis","'twill","'til","'bout","'nuff","'round","'cause"); + $cockneyreplace = array("’tain’t","’twere","’twas","’tis","’twill","’til","’bout","’nuff","’round","’cause"); + } - // if a plugin has provided an autocorrect array, use it - if ( isset($wp_cockneyreplace) ) { - $cockney = array_keys($wp_cockneyreplace); - $cockneyreplace = array_values($wp_cockneyreplace); - } else { - $cockney = array("'tain't","'twere","'twas","'tis","'twill","'til","'bout","'nuff","'round","'cause"); - $cockneyreplace = array("’tain’t","’twere","’twas","’tis","’twill","’til","’bout","’nuff","’round","’cause"); + $static_characters = array_merge(array('---', ' -- ', '--', ' - ', 'xn–', '...', '``', '\'s', '\'\'', ' (tm)'), $cockney); + $static_replacements = array_merge(array('—', ' — ', '–', ' – ', 'xn--', '…', $opening_quote, '’s', $closing_quote, ' ™'), $cockneyreplace); + + $dynamic_characters = array('/\'(\d\d(?:’|\')?s)/', '/(\s|\A|[([{<]|")\'/', '/(\d+)"/', '/(\d+)\'/', '/(\S)\'([^\'\s])/', '/(\s|\A|[([{<])"(?!\s)/', '/"(\s|\S|\Z)/', '/\'([\s.]|\Z)/', '/(\d+)x(\d+)/'); + $dynamic_replacements = array('’$1','$1‘', '$1″', '$1′', '$1’$2', '$1' . $opening_quote . '$2', $closing_quote . '$1', '’$1', '$1×$2'); + + $static_setup = true; } - $static_characters = array_merge(array('---', ' -- ', '--', 'xn–', '...', '``', '\'s', '\'\'', ' (tm)'), $cockney); - $static_replacements = array_merge(array('—', ' — ', '–', 'xn--', '…', '“', '’s', '”', ' ™'), $cockneyreplace); + // Transform into regexp sub-expression used in _wptexturize_pushpop_element + // Must do this everytime in case plugins use these filters in a context sensitive manner + $no_texturize_tags = '(' . implode('|', apply_filters('no_texturize_tags', $default_no_texturize_tags) ) . ')'; + $no_texturize_shortcodes = '(' . implode('|', apply_filters('no_texturize_shortcodes', $default_no_texturize_shortcodes) ) . ')'; - $dynamic_characters = array('/\'(\d\d(?:’|\')?s)/', '/(\s|\A|")\'/', '/(\d+)"/', '/(\d+)\'/', '/(\S)\'([^\'\s])/', '/(\s|\A)"(?!\s)/', '/"(\s|\S|\Z)/', '/\'([\s.]|\Z)/', '/(\d+)x(\d+)/'); - $dynamic_replacements = array('’$1','$1‘', '$1″', '$1′', '$1’$2', '$1“$2', '”$1', '’$1', '$1×$2'); + $no_texturize_tags_stack = array(); + $no_texturize_shortcodes_stack = array(); for ( $i = 0; $i < $stop; $i++ ) { $curl = $textarr[$i]; - if ( !empty($curl) && '<' != $curl{0} && '[' != $curl{0} && $next && !$has_pre_parent) { // If it's not a tag + 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 $curl = str_replace($static_characters, $static_replacements, $curl); // regular expressions $curl = preg_replace($dynamic_characters, $dynamic_replacements, $curl); - } elseif (strpos($curl, '') !== false) { - $has_pre_parent = false; - } else { - $next = true; + } 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};)/', '&$1', $curl); @@ -75,6 +98,48 @@ function wptexturize($text) { return $output; } +/** + * Search for disabled element tags. Push element to stack on tag open and pop + * on tag close. Assumes first character of $text is tag opening. + * + * @access private + * @since 2.9.0 + * + * @param string $text Text to check. First character is assumed to be $opening + * @param array $stack Array used as stack of opened tag elements + * @param string $disabled_elements Tags to match against formatted as regexp sub-expression + * @param string $opening Tag opening character, assumed to be 1 character long + * @param string $opening Tag closing character + * @return object + */ +function _wptexturize_pushpop_element($text, &$stack, $disabled_elements, $opening = '<', $closing = '>') { + // Check if it is a closing tag -- otherwise assume opening tag + if (strncmp($opening . '/', $text, 2)) { + // Opening? Check $text+1 against disabled elements + if (preg_match('/^' . $disabled_elements . '\b/', substr($text, 1), $matches)) { + /* + * This disables texturize until we find a closing tag of our type + * (e.g.
) even if there was invalid nesting before that
+			 * 
+			 * Example: in the case 
sadsadasd"baba"
+ * "baba" won't be texturize + */ + + array_push($stack, $matches[1]); + } + } else { + // Closing? Check $text+2 against disabled elements + $c = preg_quote($closing, '/'); + if (preg_match('/^' . $disabled_elements . $c . '/', substr($text, 2), $matches)) { + $last = array_pop($stack); + + // Make sure it matches the opening tag + if ($last != $matches[1]) + array_push($stack, $last); + } + } +} + /** * Accepts matches array from preg_replace_callback in wpautop() or a string. * @@ -114,10 +179,13 @@ function clean_pre($matches) { * @return string Text which has been converted into correct paragraph tags. */ function wpautop($pee, $br = 1) { + + if ( trim($pee) === '' ) + return ''; $pee = $pee . "\n"; // just to make things a little easier, pad the end $pee = preg_replace('|
\s*
|', "\n\n", $pee); // Space things out a little - $allblocks = '(?:table|thead|tfoot|caption|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|form|map|area|blockquote|address|math|style|input|p|h[1-6]|hr)'; + $allblocks = '(?:table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|form|map|area|blockquote|address|math|style|input|p|h[1-6]|hr|fieldset|legend)'; $pee = preg_replace('!(<' . $allblocks . '[^>]*>)!', "\n$1", $pee); $pee = preg_replace('!()!', "$1\n\n", $pee); $pee = str_replace(array("\r\n", "\r"), "\n", $pee); // cross-platform newlines @@ -131,9 +199,8 @@ function wpautop($pee, $br = 1) { $pee = ''; foreach ( $pees as $tinkle ) $pee .= '

' . trim($tinkle, "\n") . "

\n"; - $pee = preg_replace('|

\s*?

|', '', $pee); // under certain strange conditions it could create a P of entirely whitespace - $pee = preg_replace('!

([^<]+)\s*?(]*>)!', "

$1

$2", $pee); - $pee = preg_replace( '|

|', "$1

", $pee ); + $pee = preg_replace('|

\s*

|', '', $pee); // under certain strange conditions it could create a P of entirely whitespace + $pee = preg_replace('!

([^<]+)!', "

$1

", $pee); $pee = preg_replace('!

\s*(]*>)\s*

!', "$1", $pee); // don't pee all over a tag $pee = preg_replace("|

(|", "$1", $pee); // problem with nested lists $pee = preg_replace('|

]*)>|i', "

", $pee); @@ -148,9 +215,30 @@ function wpautop($pee, $br = 1) { $pee = preg_replace('!(]*>)\s*
!', "$1", $pee); $pee = preg_replace('!
(\s*]*>)!', '$1', $pee); if (strpos($pee, ')(.*?)

!is', 'clean_pre', $pee ); + $pee = preg_replace_callback('!(]*>)(.*?)!is', 'clean_pre', $pee ); $pee = preg_replace( "|\n

$|", '

', $pee ); - $pee = preg_replace('/

\s*?(' . get_shortcode_regex() . ')\s*<\/p>/s', '$1', $pee); // don't auto-p wrap shortcodes that stand alone + + return $pee; +} + +/** + * Don't auto-p wrap shortcodes that stand alone + * + * Ensures that shortcodes are not wrapped in <

>...<

>. + * + * @since 2.9.0 + * + * @param string $pee The content. + * @return string The filtered content. + */ +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('/

\\s*?(\\[(' . $tagregexp . ')\\b.*?\\/?\\](?:.+?\\[\\/\\2\\])?)\\s*<\\/p>/s', '$1', $pee); + } return $pee; } @@ -158,26 +246,29 @@ function wpautop($pee, $br = 1) { /** * Checks to see if a string is utf8 encoded. * - * @author bmorel at ssi dot fr + * NOTE: This function checks for 5-Byte sequences, UTF8 + * has Bytes Sequences with a maximum length of 4. * + * @author bmorel at ssi dot fr (modified) * @since 1.2.1 * - * @param string $Str The string to be checked - * @return bool True if $Str fits a UTF-8 model, false otherwise. + * @param string $str The string to be checked + * @return bool True if $str fits a UTF-8 model, false otherwise. */ -function seems_utf8($Str) { # by bmorel at ssi dot fr - $length = strlen($Str); +function seems_utf8($str) { + $length = strlen($str); for ($i=0; $i < $length; $i++) { - if (ord($Str[$i]) < 0x80) continue; # 0bbbbbbb - elseif ((ord($Str[$i]) & 0xE0) == 0xC0) $n=1; # 110bbbbb - elseif ((ord($Str[$i]) & 0xF0) == 0xE0) $n=2; # 1110bbbb - elseif ((ord($Str[$i]) & 0xF8) == 0xF0) $n=3; # 11110bbb - elseif ((ord($Str[$i]) & 0xFC) == 0xF8) $n=4; # 111110bb - elseif ((ord($Str[$i]) & 0xFE) == 0xFC) $n=5; # 1111110b + $c = ord($str[$i]); + if ($c < 0x80) $n = 0; # 0bbbbbbb + elseif (($c & 0xE0) == 0xC0) $n=1; # 110bbbbb + elseif (($c & 0xF0) == 0xE0) $n=2; # 1110bbbb + elseif (($c & 0xF8) == 0xF0) $n=3; # 11110bbb + elseif (($c & 0xFC) == 0xF8) $n=4; # 111110bb + elseif (($c & 0xFE) == 0xFC) $n=5; # 1111110b else return false; # Does not match any model for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ? - if ((++$i == $length) || ((ord($Str[$i]) & 0xC0) != 0x80)) - return false; + if ((++$i == $length) || ((ord($str[$i]) & 0xC0) != 0x80)) + return false; } } return true; @@ -199,8 +290,7 @@ function seems_utf8($Str) { # by bmorel at ssi dot fr * @param boolean $double_encode Optional. Whether or not to encode existing html entities. Default is false. * @return string The encoded text with HTML entities. */ -function wp_specialchars( $string, $quote_style = ENT_NOQUOTES, $charset = false, $double_encode = false ) -{ +function _wp_specialchars( $string, $quote_style = ENT_NOQUOTES, $charset = false, $double_encode = false ) { $string = (string) $string; if ( 0 === strlen( $string ) ) { @@ -244,7 +334,7 @@ function wp_specialchars( $string, $quote_style = ENT_NOQUOTES, $charset = false // Handle double encoding ourselves if ( !$double_encode ) { $string = wp_specialchars_decode( $string, $_quote_style ); - $string = preg_replace( '/&(#?x?[0-9]+|[a-z]+);/i', '|wp_entity|$1|/wp_entity|', $string ); + $string = preg_replace( '/&(#?x?[0-9a-z]+);/i', '|wp_entity|$1|/wp_entity|', $string ); } $string = @htmlspecialchars( $string, $quote_style, $charset ); @@ -273,11 +363,10 @@ function wp_specialchars( $string, $quote_style = ENT_NOQUOTES, $charset = false * @since 2.8 * * @param string $string The text which is to be decoded. - * @param mixed $quote_style Optional. Converts double quotes if set to ENT_COMPAT, both single and double if set to ENT_QUOTES or none if set to ENT_NOQUOTES. Also compatible with old wp_specialchars() values; converting single quotes if set to 'single', double if set to 'double' or both if otherwise set. Default is ENT_NOQUOTES. + * @param mixed $quote_style Optional. Converts double quotes if set to ENT_COMPAT, both single and double if set to ENT_QUOTES or none if set to ENT_NOQUOTES. Also compatible with old _wp_specialchars() values; converting single quotes if set to 'single', double if set to 'double' or both if otherwise set. Default is ENT_NOQUOTES. * @return string The decoded text without HTML entities. */ -function wp_specialchars_decode( $string, $quote_style = ENT_NOQUOTES ) -{ +function wp_specialchars_decode( $string, $quote_style = ENT_NOQUOTES ) { $string = (string) $string; if ( 0 === strlen( $string ) ) { @@ -289,7 +378,7 @@ function wp_specialchars_decode( $string, $quote_style = ENT_NOQUOTES ) return $string; } - // Match the previous behaviour of wp_specialchars() when the $quote_style is not an accepted value + // Match the previous behaviour of _wp_specialchars() when the $quote_style is not an accepted value if ( empty( $quote_style ) ) { $quote_style = ENT_NOQUOTES; } elseif ( !in_array( $quote_style, array( 0, 2, 3, 'single', 'double' ), true ) ) { @@ -334,8 +423,7 @@ function wp_specialchars_decode( $string, $quote_style = ENT_NOQUOTES ) * @param boolean $strip Optional. Whether to attempt to strip out invalid UTF8. Default is false. * @return string The checked text. */ -function wp_check_invalid_utf8( $string, $strip = false ) -{ +function wp_check_invalid_utf8( $string, $strip = false ) { $string = (string) $string; if ( 0 === strlen( $string ) ) { @@ -565,27 +653,60 @@ function remove_accents($string) { } /** - * Filters certain characters from the file name. + * Sanitizes a filename replacing whitespace with dashes * - * Turns all strings to lowercase removing most characters except alphanumeric - * with spaces, dashes and periods. All spaces and underscores are converted to - * dashes. Multiple dashes are converted to a single dash. Finally, if the file - * name ends with a dash, it is removed. + * Removes special characters that are illegal in filenames on certain + * operating systems and special characters requiring special escaping + * to manipulate at the command line. Replaces spaces and consecutive + * dashes with a single dash. Trim period, dash and underscore from beginning + * and end of filename. * * @since 2.1.0 * - * @param string $name The file name - * @return string Sanitized file name - */ -function sanitize_file_name( $name ) { // Like sanitize_title, but with periods - $name = strtolower( $name ); - $name = preg_replace('/&.+?;/', '', $name); // kill entities - $name = str_replace( '_', '-', $name ); - $name = preg_replace('/[^a-z0-9\s-.]/', '', $name); - $name = preg_replace('/\s+/', '-', $name); - $name = preg_replace('|-+|', '-', $name); - $name = trim($name, '-'); - return $name; + * @param string $filename The filename to be sanitized + * @return string The sanitized filename + */ +function sanitize_file_name( $filename ) { + $filename_raw = $filename; + $special_chars = array("?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}", chr(0)); + $special_chars = apply_filters('sanitize_file_name_chars', $special_chars, $filename_raw); + $filename = str_replace($special_chars, '', $filename); + $filename = preg_replace('/[\s-]+/', '-', $filename); + $filename = trim($filename, '.-_'); + + // Split the filename into a base and extension[s] + $parts = explode('.', $filename); + + // Return if only one extension + if ( count($parts) <= 2 ) + return apply_filters('sanitize_file_name', $filename, $filename_raw); + + // Process multiple extensions + $filename = array_shift($parts); + $extension = array_pop($parts); + $mimes = get_allowed_mime_types(); + + // Loop over any intermediate extensions. Munge them with a trailing underscore if they are a 2 - 5 character + // long alpha string not in the extension whitelist. + foreach ( (array) $parts as $part) { + $filename .= '.' . $part; + + if ( preg_match("/^[a-zA-Z]{2,5}\d?$/", $part) ) { + $allowed = false; + foreach ( $mimes as $ext_preg => $mime_match ) { + $ext_preg = '!(^' . $ext_preg . ')$!i'; + if ( preg_match( $ext_preg, $part ) ) { + $allowed = true; + break; + } + } + if ( !$allowed ) + $filename .= '_'; + } + } + $filename .= '.' . $extension; + + return apply_filters('sanitize_file_name', $filename, $filename_raw); } /** @@ -608,7 +729,7 @@ function sanitize_file_name( $name ) { // Like sanitize_title, but with periods */ function sanitize_user( $username, $strict = false ) { $raw_username = $username; - $username = strip_tags($username); + $username = wp_strip_all_tags($username); // Kill octets $username = preg_replace('|%([a-fA-F0-9][a-fA-F0-9])|', '', $username); $username = preg_replace('/&.+?;/', '', $username); // Kill entities @@ -637,8 +758,9 @@ function sanitize_user( $username, $strict = false ) { * @return string The sanitized string. */ function sanitize_title($title, $fallback_title = '') { + $raw_title = $title; $title = strip_tags($title); - $title = apply_filters('sanitize_title', $title); + $title = apply_filters('sanitize_title', $title, $raw_title); if ( '' === $title || false === $title ) $title = $fallback_title; @@ -676,6 +798,7 @@ function sanitize_title_with_dashes($title) { $title = strtolower($title); $title = preg_replace('/&.+?;/', '', $title); // kill entities + $title = str_replace('.', '-', $title); $title = preg_replace('/[^%a-z0-9 _-]/', '', $title); $title = preg_replace('/\s+/', '-', $title); $title = preg_replace('|-+|', '-', $title); @@ -702,6 +825,33 @@ function sanitize_sql_orderby( $orderby ){ return $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 + * string then it will return the alternative value supplied. + * + * @todo Expand to support the full range of CDATA that a class attribute can contain. + * + * @since 2.8.0 + * + * @param string $class The classname to be sanitized + * @param string $fallback The value to return if the sanitization end's up as an empty string. + * @return string The sanitized value + */ +function sanitize_html_class($class, $fallback){ + //Strip out any % encoded octets + $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); + + if ('' == $sanitized) + $sanitized = $fallback; + + return apply_filters('sanitize_html_class',$sanitized, $class, $fallback); +} + /** * Converts a number of characters from a string. * @@ -769,6 +919,18 @@ function convert_chars($content, $deprecated = '') { return $content; } +/** + * Callback used to change %uXXXX to &#YYY; syntax + * + * @since 2.8? + * + * @param array $matches Single Match + * @return string An HTML entity + */ +function funky_javascript_callback($matches) { + return "&#".base_convert($matches[1],16,10).";"; +} + /** * Fixes javascript bugs in browsers. * @@ -785,9 +947,10 @@ function funky_javascript_fix($text) { // Fixes for browsers' javascript bugs global $is_macIE, $is_winIE; - /** @todo use preg_replace_callback() instead */ if ( $is_winIE || $is_macIE ) - $text = preg_replace("/\%u([0-9A-F]{4,4})/e", "'&#'.base_convert('\\1',16,10).';'", $text); + $text = preg_replace_callback("/\%u([0-9A-F]{4,4})/", + "funky_javascript_callback", + $text); return $text; } @@ -848,7 +1011,7 @@ function force_balance_tags( $text ) { // clear the shifter $tagqueue = ''; // Pop or Push - if ($regex[1][0] == "/") { // End Tag + if ( isset($regex[1][0]) && '/' == $regex[1][0] ) { // End Tag $tag = strtolower(substr($regex[1],1)); // if too many closing tags if($stacksize <= 0) { @@ -1048,7 +1211,7 @@ function addslashes_gpc($gpc) { $gpc = stripslashes($gpc); } - return $wpdb->escape($gpc); + return esc_sql($gpc); } /** @@ -1122,17 +1285,13 @@ function antispambot($emailaddy, $mailto=0) { * @return string HTML A element with URI address. */ function _make_url_clickable_cb($matches) { - $ret = ''; $url = $matches[2]; - $url = clean_url($url); + + $url = esc_url($url); if ( empty($url) ) return $matches[0]; - // removed trailing [.,;:] from URL - if ( in_array(substr($url, -1), array('.', ',', ';', ':')) === true ) { - $ret = substr($url, -1); - $url = substr($url, 0, strlen($url)-1); - } - return $matches[1] . "$url" . $ret; + + return $matches[1] . "$url"; } /** @@ -1151,15 +1310,16 @@ function _make_web_ftp_clickable_cb($matches) { $ret = ''; $dest = $matches[2]; $dest = 'http://' . $dest; - $dest = clean_url($dest); + $dest = esc_url($dest); if ( empty($dest) ) return $matches[0]; - // removed trailing [,;:] from URL - if ( in_array(substr($dest, -1), array('.', ',', ';', ':')) === true ) { + + // removed trailing [.,;:)] from URL + if ( in_array( substr($dest, -1), array('.', ',', ';', ':', ')') ) === true ) { $ret = substr($dest, -1); $dest = substr($dest, 0, strlen($dest)-1); } - return $matches[1] . "$dest" . $ret; + return $matches[1] . "$dest$ret"; } /** @@ -1193,8 +1353,8 @@ function _make_email_clickable_cb($matches) { function make_clickable($ret) { $ret = ' ' . $ret; // in testing, using arrays here was found to be faster - $ret = preg_replace_callback('#([\s>])([\w]+?://[\w\\x80-\\xff\#$%&~/.\-;:=,?@\[\]+]*)#is', '_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>])(\()?([\w]+?://(?:[\w\\x80-\\xff\#$%&~/=?@\[\](+-]|[.,;:](?![\s<]|(\))?([\s]|$))|(?(1)\)(?![\s<.,;:]|$)|\)))+)#is', '_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); // this one is not in an array because we need it to run last, for cleanup of accidental links within links $ret = preg_replace("#(]+?>|>))]+?>([^>]+?)#i", "$1$3", $ret); @@ -1215,7 +1375,7 @@ function wp_rel_nofollow( $text ) { // This is a pre save filter, so text is already escaped. $text = stripslashes($text); $text = preg_replace_callback('||i', 'wp_rel_nofollow_callback', $text); - $text = $wpdb->escape($text); + $text = esc_sql($text); return $text; } @@ -1236,29 +1396,61 @@ function wp_rel_nofollow_callback( $matches ) { return ""; } + +/** + * Convert one smiley code to the icon graphic file equivalent. + * + * Looks up one smiley code in the $wpsmiliestrans global array and returns an + * string for that smiley. + * + * @global array $wpsmiliestrans + * @since 2.8.0 + * + * @param string $smiley Smiley code to convert to image. + * @return string Image string for smiley. + */ +function translate_smiley($smiley) { + global $wpsmiliestrans; + + if (count($smiley) == 0) { + return ''; + } + + $siteurl = get_option( 'siteurl' ); + + $smiley = trim(reset($smiley)); + $img = $wpsmiliestrans[$smiley]; + $smiley_masked = esc_attr($smiley); + + $srcurl = apply_filters('smilies_src', "$siteurl/wp-includes/images/smilies/$img", $img, $siteurl); + + return " $smiley_masked "; +} + + /** * Convert text equivalent of smilies to images. * - * Will only convert smilies if the option 'use_smilies' is true and the globals - * used in the function aren't empty. + * Will only convert smilies if the option 'use_smilies' is true and the global + * used in the function isn't empty. * * @since 0.71 - * @uses $wp_smiliessearch, $wp_smiliesreplace Smiley replacement arrays. + * @uses $wp_smiliessearch * * @param string $text Content to convert smilies from text. * @return string Converted content with text smilies replaced with images. */ function convert_smilies($text) { - global $wp_smiliessearch, $wp_smiliesreplace; + global $wp_smiliessearch; $output = ''; - if ( get_option('use_smilies') && !empty($wp_smiliessearch) && !empty($wp_smiliesreplace) ) { + if ( get_option('use_smilies') && !empty($wp_smiliessearch) ) { // HTML loop taken from texturize function, could possible be consolidated $textarr = preg_split("/(<.*>)/U", $text, -1, PREG_SPLIT_DELIM_CAPTURE); // capture the tags as well as in between $stop = count($textarr);// loop stuff for ($i = 0; $i < $stop; $i++) { $content = $textarr[$i]; if ((strlen($content) > 0) && ('<' != $content{0})) { // If it's not a tag - $content = preg_replace($wp_smiliessearch, $wp_smiliesreplace, $content); + $content = preg_replace_callback($wp_smiliessearch, 'translate_smiley', $content); } $output .= $content; } @@ -1270,24 +1462,76 @@ function convert_smilies($text) { } /** - * Checks to see if the text is a valid email address. + * Verifies that an email is valid. + * + * Does not grok i18n domains. Not RFC compliant. * * @since 0.71 * - * @param string $user_email The email address to be checked. - * @return bool Returns true if valid, otherwise false. + * @param string $email Email address to verify. + * @param boolean $check_dns Whether to check the DNS for the domain using checkdnsrr(). + * @return string|bool Either false or the valid email address. */ -function is_email($user_email) { - $chars = "/^([a-z0-9+_]|\\-|\\.)+@(([a-z0-9_]|\\-)+\\.)+[a-z]{2,6}\$/i"; - if (strpos($user_email, '@') !== false && strpos($user_email, '.') !== false) { - if (preg_match($chars, $user_email)) { - return true; - } else { - return false; +function is_email( $email, $check_dns = false ) { + // Test for the minimum length the email can be + if ( strlen( $email ) < 3 ) { + return apply_filters( 'is_email', false, $email, 'email_too_short' ); + } + + // Test for an @ character after the first position + if ( strpos( $email, '@', 1 ) === false ) { + return apply_filters( 'is_email', false, $email, 'email_no_at' ); + } + + // Split out the local and domain parts + list( $local, $domain ) = explode( '@', $email, 2 ); + + // LOCAL PART + // Test for invalid characters + if ( !preg_match( '/^[a-zA-Z0-9!#$%&\'*+\/=?^_`{|}~\.-]+$/', $local ) ) { + return apply_filters( 'is_email', false, $email, 'local_invalid_chars' ); + } + + // DOMAIN PART + // Test for sequences of periods + if ( preg_match( '/\.{2,}/', $domain ) ) { + return apply_filters( 'is_email', false, $email, 'domain_period_sequence' ); + } + + // Test for leading and trailing periods and whitespace + if ( trim( $domain, " \t\n\r\0\x0B." ) !== $domain ) { + return apply_filters( 'is_email', false, $email, 'domain_period_limits' ); + } + + // Split the domain into subs + $subs = explode( '.', $domain ); + + // Assume the domain will have at least two subs + if ( 2 > count( $subs ) ) { + return apply_filters( 'is_email', false, $email, 'domain_no_periods' ); + } + + // Loop through each sub + foreach ( $subs as $sub ) { + // Test for leading and trailing hyphens and whitespace + if ( trim( $sub, " \t\n\r\0\x0B-" ) !== $sub ) { + return apply_filters( 'is_email', false, $email, 'sub_hyphen_limits' ); + } + + // Test for invalid characters + if ( !preg_match('/^[a-z0-9-]+$/i', $sub ) ) { + return apply_filters( 'is_email', false, $email, 'sub_invalid_chars' ); } - } else { - return false; } + + // DNS + // Check the domain has a valid MX and A resource record + if ( $check_dns && function_exists( 'checkdnsrr' ) && !( checkdnsrr( $domain . '.', 'MX' ) || checkdnsrr( $domain . '.', 'A' ) ) ) { + return apply_filters( 'is_email', false, $email, 'dns_no_rr' ); + } + + // Congratulations your email made it! + return apply_filters( 'is_email', $email, $email, null ); } /** @@ -1314,18 +1558,20 @@ function wp_iso_descrambler($string) { * Returns a date in the GMT equivalent. * * Requires and returns a date in the Y-m-d H:i:s format. Simply subtracts the - * value of the 'gmt_offset' option. + * value of the 'gmt_offset' option. Return format can be overridden using the + * $format parameter * * @since 1.2.0 * * @uses get_option() to retrieve the the value of 'gmt_offset'. * @param string $string The date to be converted. + * @param string $format The format string for the returned date (default is Y-m-d H:i:s) * @return string GMT version of the date provided. */ -function get_gmt_from_date($string) { +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); $string_time = gmmktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]); - $string_gmt = gmdate('Y-m-d H:i:s', $string_time - get_option('gmt_offset') * 3600); + $string_gmt = gmdate($format, $string_time - get_option('gmt_offset') * 3600); return $string_gmt; } @@ -1333,17 +1579,18 @@ function get_gmt_from_date($string) { * Converts a GMT date into the correct format for the blog. * * Requires and returns in the Y-m-d H:i:s format. Simply adds the value of - * gmt_offset. + * gmt_offset.Return format can be overridden using the $format parameter * * @since 1.2.0 * * @param string $string The date to be converted. + * @param string $format The format string for the returned date (default is Y-m-d H:i:s) * @return string Formatted date relative to the GMT offset. */ -function get_date_from_gmt($string) { +function get_date_from_gmt($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); $string_time = gmmktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]); - $string_localtime = gmdate('Y-m-d H:i:s', $string_time + get_option('gmt_offset')*3600); + $string_localtime = gmdate($format, $string_time + get_option('gmt_offset')*3600); return $string_localtime; } @@ -1425,8 +1672,78 @@ function popuplinks($text) { * @param string $email Email address to filter. * @return string Filtered email address. */ -function sanitize_email($email) { - return preg_replace('/[^a-z0-9+_.@-]/i', '', $email); +function sanitize_email( $email ) { + // Test for the minimum length the email can be + if ( strlen( $email ) < 3 ) { + return apply_filters( 'sanitize_email', '', $email, 'email_too_short' ); + } + + // Test for an @ character after the first position + if ( strpos( $email, '@', 1 ) === false ) { + return apply_filters( 'sanitize_email', '', $email, 'email_no_at' ); + } + + // Split out the local and domain parts + list( $local, $domain ) = explode( '@', $email, 2 ); + + // LOCAL PART + // Test for invalid characters + $local = preg_replace( '/[^a-zA-Z0-9!#$%&\'*+\/=?^_`{|}~\.-]/', '', $local ); + if ( '' === $local ) { + return apply_filters( 'sanitize_email', '', $email, 'local_invalid_chars' ); + } + + // DOMAIN PART + // Test for sequences of periods + $domain = preg_replace( '/\.{2,}/', '', $domain ); + if ( '' === $domain ) { + return apply_filters( 'sanitize_email', '', $email, 'domain_period_sequence' ); + } + + // Test for leading and trailing periods and whitespace + $domain = trim( $domain, " \t\n\r\0\x0B." ); + if ( '' === $domain ) { + return apply_filters( 'sanitize_email', '', $email, 'domain_period_limits' ); + } + + // Split the domain into subs + $subs = explode( '.', $domain ); + + // Assume the domain will have at least two subs + if ( 2 > count( $subs ) ) { + return apply_filters( 'sanitize_email', '', $email, 'domain_no_periods' ); + } + + // Create an array that will contain valid subs + $new_subs = array(); + + // Loop through each sub + foreach ( $subs as $sub ) { + // Test for leading and trailing hyphens + $sub = trim( $sub, " \t\n\r\0\x0B-" ); + + // Test for invalid characters + $sub = preg_replace( '/^[^a-z0-9-]+$/i', '', $sub ); + + // If there's anything left, add it to the valid subs + if ( '' !== $sub ) { + $new_subs[] = $sub; + } + } + + // If there aren't 2 or more valid subs + if ( 2 > count( $new_subs ) ) { + return apply_filters( 'sanitize_email', '', $email, 'domain_no_valid_subs' ); + } + + // Join valid subs into the new domain + $domain = join( '.', $new_subs ); + + // Put the email back together + $email = $local . '@' . $domain; + + // Congratulations your email made it! + return apply_filters( 'sanitize_email', $email, $email, null ); } /** @@ -1450,19 +1767,19 @@ function human_time_diff( $from, $to = '' ) { if ($mins <= 1) { $mins = 1; } - $since = sprintf(__ngettext('%s min', '%s mins', $mins), $mins); + $since = sprintf(_n('%s min', '%s mins', $mins), $mins); } else if (($diff <= 86400) && ($diff > 3600)) { $hours = round($diff / 3600); if ($hours <= 1) { $hours = 1; } - $since = sprintf(__ngettext('%s hour', '%s hours', $hours), $hours); + $since = sprintf(_n('%s hour', '%s hours', $hours), $hours); } elseif ($diff >= 86400) { $days = round($diff / 86400); if ($days <= 1) { $days = 1; } - $since = sprintf(__ngettext('%s day', '%s days', $days), $days); + $since = sprintf(_n('%s day', '%s days', $days), $days); } return $since; } @@ -1471,15 +1788,19 @@ function human_time_diff( $from, $to = '' ) { * Generates an excerpt from the content, if needed. * * The excerpt word amount will be 55 words and if the amount is greater than - * that, then the string '[...]' will be appended to the excerpt. If the string + * that, then the string ' [...]' will be appended to the excerpt. If the string * is less than 55 words, then the content will be returned as is. * + * The 55 word limit can be modified by plugins/themes using the excerpt_length filter + * The ' [...]' string can be modified by plugins/themes using the excerpt_more filter + * * @since 1.5.0 * - * @param string $text The exerpt. If set to empty an excerpt is generated. + * @param string $text The excerpt. If set to empty an excerpt is generated. * @return string The excerpt. */ function wp_trim_excerpt($text) { + $raw_excerpt = $text; if ( '' == $text ) { $text = get_the_content(''); @@ -1489,14 +1810,15 @@ function wp_trim_excerpt($text) { $text = str_replace(']]>', ']]>', $text); $text = strip_tags($text); $excerpt_length = apply_filters('excerpt_length', 55); + $excerpt_more = apply_filters('excerpt_more', ' ' . '[...]'); $words = explode(' ', $text, $excerpt_length + 1); if (count($words) > $excerpt_length) { array_pop($words); - array_push($words, '[...]'); $text = implode(' ', $words); + $text = $text . $excerpt_more; } } - return $text; + return apply_filters('wp_trim_excerpt', $text, $raw_excerpt); } /** @@ -1815,7 +2137,7 @@ function wp_htmledit_pre($output) { * 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) amperstands are also replaced. The 'esc_url' filter * is applied to the returned cleaned URL. * * @since 1.2.0 @@ -1832,16 +2154,16 @@ function clean_url( $url, $protocols = null, $context = 'display' ) { $original_url = $url; if ('' == $url) return $url; - $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$*\'()\\x80-\\xff]|i', '', $url); - $strip = array('%0d', '%0a'); - $url = str_replace($strip, '', $url); + $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\\x80-\\xff]|i', '', $url); + $strip = array('%0d', '%0a', '%0D', '%0A'); + $url = _deep_replace($strip, $url); $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). */ if ( strpos($url, ':') === false && - substr( $url, 0, 1 ) != '/' && !preg_match('/^[a-z0-9-]+?\.php/i', $url) ) + substr( $url, 0, 1 ) != '/' && substr( $url, 0, 1 ) != '#' && !preg_match('/^[a-z0-9-]+?\.php/i', $url) ) $url = 'http://' . $url; // Replace ampersands and single quotes only when displaying. @@ -1859,9 +2181,91 @@ function clean_url( $url, $protocols = null, $context = 'display' ) { } /** - * Performs clean_url() for database usage. + * Perform a deep string replace operation to ensure the values in $search are no longer present + * + * Repeats the replacement operation until it no longer replaces anything so as to remove "nested" values + * e.g. $subject = '%0%0%0DDD', $search ='%0D', $result ='' rather than the '%0%0DD' that + * str_replace would return + * + * @since 2.8.1 + * @access private + * + * @param string|array $search + * @param string $subject + * @return string The processed string + */ +function _deep_replace($search, $subject){ + $found = true; + while($found) { + $found = false; + foreach( (array) $search as $val ) { + while(strpos($subject, $val) !== false) { + $found = true; + $subject = str_replace($val, '', $subject); + } + } + } + + return $subject; +} + +/** + * Escapes data for use in a MySQL query * - * @see clean_url() + * This is just a handy shortcut for $wpdb->escape(), for completeness' sake + * + * @since 2.8.0 + * @param string $sql Unescaped SQL data + * @return string The cleaned $sql + */ +function esc_sql( $sql ) { + global $wpdb; + return $wpdb->escape( $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 'esc_url' filter + * is applied to the returned cleaned URL. + * + * @since 2.8.0 + * @uses esc_url() + * @uses wp_kses_bad_protocol() To only permit protocols in the URL set + * via $protocols or the common ones set in the function. + * + * @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. + * @return string The cleaned $url after the 'cleaned_url' filter is applied. + */ +function esc_url( $url, $protocols = null ) { + return clean_url( $url, $protocols, 'display' ); +} + +/** + * Performs esc_url() for database usage. + * + * @see esc_url() + * @see esc_url() + * + * @since 2.8.0 + * + * @param string $url The URL to be cleaned. + * @param array $protocols An array of acceptable protocols. + * @return string The cleaned URL. + */ +function esc_url_raw( $url, $protocols = null ) { + return clean_url( $url, $protocols, 'db' ); +} + +/** + * Performs esc_url() for database or redirect usage. + * + * @see esc_url() + * @deprecated 2.8.0 * * @since 2.3.1 * @@ -1890,37 +2294,100 @@ function htmlentities2($myHTML) { } /** - * Escape single quotes, specialchar double quotes, and fix line endings. + * Escape single quotes, htmlspecialchar " < > &, and fix line endings. * + * Escapes text strings for echoing in JS, both inline (for example in onclick="...") + * and inside