X-Git-Url: https://scripts.mit.edu/gitweb/autoinstalls/wordpress.git/blobdiff_plain/177fd6fefd2e3d5a0ea6591c71d660cabdb3c1a4..refs/tags/wordpress-4.3.1:/wp-includes/formatting.php diff --git a/wp-includes/formatting.php b/wp-includes/formatting.php index 052445d1..640e136f 100644 --- a/wp-includes/formatting.php +++ b/wp-includes/formatting.php @@ -1,215 +1,1107 @@ |\[.*\])/Us', $text, -1, PREG_SPLIT_DELIM_CAPTURE); - $stop = count($textarr); - - // 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"); +/** + * Replaces common plain text characters into formatted entities + * + * As an example, + * + * 'cause today's effort makes it worth tomorrow's "holiday" ... + * + * Becomes: + * + * ’cause today’s effort makes it worth tomorrow’s “holiday” … + * + * Code within certain html blocks are skipped. + * + * Do not use this function before the 'init' action hook; everything will break. + * + * @since 0.71 + * + * @global array $wp_cockneyreplace Array of formatted entities for certain common phrases + * @global array $shortcode_tags + * @staticvar array $static_characters + * @staticvar array $static_replacements + * @staticvar array $dynamic_characters + * @staticvar array $dynamic_replacements + * @staticvar array $default_no_texturize_tags + * @staticvar array $default_no_texturize_shortcodes + * @staticvar bool $run_texturize + * + * @param string $text The text to be formatted + * @param bool $reset Set to true for unit testing. Translated patterns will reset. + * @return string The string replaced with html entities + */ +function wptexturize( $text, $reset = false ) { + global $wp_cockneyreplace, $shortcode_tags; + static $static_characters = null, + $static_replacements = null, + $dynamic_characters = null, + $dynamic_replacements = null, + $default_no_texturize_tags = null, + $default_no_texturize_shortcodes = null, + $run_texturize = true, + $apos = null, + $prime = null, + $double_prime = null, + $opening_quote = null, + $closing_quote = null, + $opening_single_quote = null, + $closing_single_quote = null, + $open_q_flag = '', + $open_sq_flag = '', + $apos_flag = ''; + + // If there's nothing to do, just stop. + if ( empty( $text ) || false === $run_texturize ) { + return $text; } - $static_characters = array_merge(array('---', ' -- ', '--', 'xn–', '...', '``', '\'s', '\'\'', ' (tm)'), $cockney); - $static_replacements = array_merge(array('—', ' — ', '–', 'xn--', '…', '“', '’s', '”', ' ™'), $cockneyreplace); + // Set up static variables. Run once only. + if ( $reset || ! isset( $static_characters ) ) { + /** + * Filter whether to skip running wptexturize(). + * + * Passing false to the filter will effectively short-circuit wptexturize(). + * returning the original text passed to the function instead. + * + * The filter runs only once, the first time wptexturize() is called. + * + * @since 4.0.0 + * + * @see wptexturize() + * + * @param bool $run_texturize Whether to short-circuit wptexturize(). + */ + $run_texturize = apply_filters( 'run_wptexturize', $run_texturize ); + if ( false === $run_texturize ) { + return $text; + } - $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'); + /* translators: opening curly double quote */ + $opening_quote = _x( '“', 'opening curly double quote' ); + /* translators: closing curly double quote */ + $closing_quote = _x( '”', 'closing curly double quote' ); - for ( $i = 0; $i < $stop; $i++ ) { - $curl = $textarr[$i]; - - if (isset($curl{0}) && '<' != $curl{0} && '[' != $curl{0} && $next) { // If it's not a tag - // static strings - $curl = str_replace($static_characters, $static_replacements, $curl); - // regular expressions - $curl = preg_replace($dynamic_characters, $dynamic_replacements, $curl); - } elseif (strpos($curl, ' array(), 'quote' => array(), 'dash' => array() ); + $dynamic_replacements = array( 'apos' => array(), 'quote' => array(), 'dash' => array() ); + $dynamic = array(); + $spaces = wp_spaces_regexp(); + + // '99' and '99" are ambiguous among other patterns; assume it's an abbreviated year at the end of a quotation. + if ( "'" !== $apos || "'" !== $closing_single_quote ) { + $dynamic[ '/\'(\d\d)\'(?=\Z|[.,:;!?)}\-\]]|>|' . $spaces . ')/' ] = $apos_flag . '$1' . $closing_single_quote; + } + if ( "'" !== $apos || '"' !== $closing_quote ) { + $dynamic[ '/\'(\d\d)"(?=\Z|[.,:;!?)}\-\]]|>|' . $spaces . ')/' ] = $apos_flag . '$1' . $closing_quote; + } + + // '99 '99s '99's (apostrophe) But never '9 or '99% or '999 or '99.0. + if ( "'" !== $apos ) { + $dynamic[ '/\'(?=\d\d(?:\Z|(?![%\d]|[.,]\d)))/' ] = $apos_flag; + } + + // Quoted Numbers like '0.42' + if ( "'" !== $opening_single_quote && "'" !== $closing_single_quote ) { + $dynamic[ '/(?<=\A|' . $spaces . ')\'(\d[.,\d]*)\'/' ] = $open_sq_flag . '$1' . $closing_single_quote; + } + + // Single quote at start, or preceded by (, {, <, [, ", -, or spaces. + if ( "'" !== $opening_single_quote ) { + $dynamic[ '/(?<=\A|[([{"\-]|<|' . $spaces . ')\'/' ] = $open_sq_flag; + } + + // Apostrophe in a word. No spaces, double apostrophes, or other punctuation. + if ( "'" !== $apos ) { + $dynamic[ '/(? is found. + . '-(?!->)' // Dash not followed by end of comment. + . '[^\-]*+' // Consume non-dashes. + . ')*+' // Loop possessively. + . '(?:-->)?'; // End of comment. If not found, match all input. + + $shortcode_regex = + '\[' // Find start of shortcode. + . '[\/\[]?' // Shortcodes may begin with [/ or [[ + . $tagregexp // Only match registered shortcodes, because performance. + . '(?:' + . '[^\[\]<>]+' // Shortcodes do not contain other shortcodes. Quantifier critical. + . '|' + . '<[^\[\]>]*>' // HTML elements permitted. Prevents matching ] before >. + . ')*+' // Possessive critical. + . '\]' // Find end of shortcode. + . '\]?'; // Shortcodes may end with ]] + + $regex = + '/(' // Capture the entire match. + . '<' // Find start of element. + . '(?(?=!--)' // Is this a comment? + . $comment_regex // Find end of comment. + . '|' + . '[^>]*>' // Find end of element. + . ')' + . '|' + . $shortcode_regex // Find shortcodes. + . ')/s'; + + $textarr = preg_split( $regex, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); + + foreach ( $textarr as &$curl ) { + // Only call _wptexturize_pushpop_element if $curl is a delimiter. + $first = $curl[0]; + if ( '<' === $first && ''; + $quote_pattern = "/$needle(?=\\Z|[.,:;!?)}\\-\\]]|>|" . $spaces . ")/"; + $prime_pattern = "/(?<=\\d)$needle/"; + $flag_after_digit = "/(?<=\\d)$flag/"; + $flag_no_digit = "/(? &$sentence ) { + if ( false === strpos( $sentence, $needle ) ) { + continue; + } elseif ( 0 !== $key && 0 === substr_count( $sentence, $close_quote ) ) { + $sentence = preg_replace( $quote_pattern, $flag, $sentence, -1, $count ); + if ( $count > 1 ) { + // This sentence appears to have multiple closing quotes. Attempt Vulcan logic. + $sentence = preg_replace( $flag_no_digit, $close_quote, $sentence, -1, $count2 ); + if ( 0 === $count2 ) { + // Try looking for a quote followed by a period. + $count2 = substr_count( $sentence, "$flag." ); + if ( $count2 > 0 ) { + // Assume the rightmost quote-period match is the end of quotation. + $pos = strrpos( $sentence, "$flag." ); + } else { + // When all else fails, make the rightmost candidate a closing quote. + // This is most likely to be problematic in the context of bug #18549. + $pos = strrpos( $sentence, $flag ); + } + $sentence = substr_replace( $sentence, $close_quote, $pos, strlen( $flag ) ); + } + // Use conventional replacement on any remaining primes and quotes. + $sentence = preg_replace( $prime_pattern, $prime, $sentence ); + $sentence = preg_replace( $flag_after_digit, $prime, $sentence ); + $sentence = str_replace( $flag, $close_quote, $sentence ); + } elseif ( 1 == $count ) { + // Found only one closing quote candidate, so give it priority over primes. + $sentence = str_replace( $flag, $close_quote, $sentence ); + $sentence = preg_replace( $prime_pattern, $prime, $sentence ); + } else { + // No closing quotes found. Just run primes pattern. + $sentence = preg_replace( $prime_pattern, $prime, $sentence ); + } + } else { + $sentence = preg_replace( $prime_pattern, $prime, $sentence ); + $sentence = preg_replace( $quote_pattern, $close_quote, $sentence ); + } + if ( '"' == $needle && false !== strpos( $sentence, '"' ) ) { + $sentence = str_replace( '"', $close_quote, $sentence ); + } + } + + return implode( $open_quote, $sentences ); +} - $text = str_replace('
', '', $text); - $text = str_replace('

', "\n", $text); - $text = str_replace('

', '', $text); +/** + * Search for disabled element tags. Push element to stack on tag open and pop + * on tag close. + * + * Assumes first char of $text is tag opening and last char is tag closing. + * Assumes second char of $text is optionally '/' to indicate closing as in . + * + * @since 2.9.0 + * @access private + * + * @param string $text Text to check. Must be a tag like `` or `[shortcode]`. + * @param array $stack List of open tag elements. + * @param array $disabled_elements The tag names to match against. Spaces are not allowed in tag names. + */ +function _wptexturize_pushpop_element( $text, &$stack, $disabled_elements ) { + // Is it an opening tag or closing tag? + if ( '/' !== $text[1] ) { + $opening_tag = true; + $name_offset = 1; + } elseif ( 0 == count( $stack ) ) { + // Stack is empty. Just stop. + return; + } else { + $opening_tag = false; + $name_offset = 2; + } - return $text; + // Parse out the tag name. + $space = strpos( $text, ' ' ); + if ( false === $space ) { + $space = -1; + } else { + $space -= $name_offset; + } + $tag = substr( $text, $name_offset, $space ); + + // Handle disabled tags. + if ( in_array( $tag, $disabled_elements ) ) { + if ( $opening_tag ) { + /* + * 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, $tag ); + } elseif ( end( $stack ) == $tag ) { + array_pop( $stack ); + } + } } -function wpautop($pee, $br = 1) { - $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)'; +/** + * Replaces double line-breaks with paragraph elements. + * + * A group of regex replaces used to identify text formatted with newlines and + * replace double line-breaks with HTML paragraph tags. The remaining line-breaks + * after conversion become <
> tags, unless $br is set to '0' or 'false'. + * + * @since 0.71 + * + * @param string $pee The text which has to be formatted. + * @param bool $br Optional. If set, this will convert all remaining line-breaks + * after paragraphing. Default true. + * @return string Text which has been converted into correct paragraph tags. + */ +function wpautop( $pee, $br = true ) { + $pre_tags = array(); + + if ( trim($pee) === '' ) + return ''; + + // Just to make things a little easier, pad the end. + $pee = $pee . "\n"; + + /* + * Pre tags shouldn't be touched by autop. + * Replace pre tags with placeholders and bring them back after autop. + */ + if ( strpos($pee, '', $pee ); + $last_pee = array_pop($pee_parts); + $pee = ''; + $i = 0; + + foreach ( $pee_parts as $pee_part ) { + $start = strpos($pee_part, ''; + + $pee .= substr( $pee_part, 0, $start ) . $name; + $i++; + } + + $pee .= $last_pee; + } + // Change multiple
s into two line breaks, which will turn into paragraphs. + $pee = preg_replace('|\s*|', "\n\n", $pee); + + $allblocks = '(?:table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary)'; + + // Add a single line break above block-level opening tags. $pee = preg_replace('!(<' . $allblocks . '[^>]*>)!', "\n$1", $pee); + + // Add a double line break below block-level closing tags. $pee = preg_replace('!()!', "$1\n\n", $pee); - $pee = str_replace(array("\r\n", "\r"), "\n", $pee); // cross-platform newlines - if ( strpos($pee, ']*)>\s*|', "", $pee); // no pee inside object/embed - $pee = preg_replace('|\s*\s*|', '', $pee); - } - $pee = preg_replace("/\n\n+/", "\n\n", $pee); // take care of duplicates - $pee = preg_replace('/\n?(.+?)(?:\n\s*\n|\z)/s', "

$1

\n", $pee); // make paragraphs, including one at the end - $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*(]*>)\s*

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

(|", "$1", $pee); // problem with nested lists + + // Standardize newline characters to "\n". + $pee = str_replace(array("\r\n", "\r"), "\n", $pee); + + // Find newlines in all elements and add placeholders. + $pee = wp_replace_in_html_tags( $pee, array( "\n" => " " ) ); + + // Collapse line breaks before and after ', $pee ); + } + + /* + * Collapse line breaks inside elements, before and elements + * so they don't get autop'd. + */ + if ( strpos( $pee, '' ) !== false ) { + $pee = preg_replace( '|(]*>)\s*|', '$1', $pee ); + $pee = preg_replace( '|\s*|', '', $pee ); + $pee = preg_replace( '%\s*(]*>)\s*%', '$1', $pee ); + } + + /* + * Collapse line breaks inside

. + foreach ( $pees as $tinkle ) { + $pee .= '

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

\n"; + } + + // Under certain strange conditions it could create a P of entirely whitespace. + $pee = preg_replace('|

\s*

|', '', $pee); + + // Add a closing

inside

,
, or
tag if missing. + $pee = preg_replace('!

([^<]+)!', "

$1

", $pee); + + // If an opening or closing block element tag is wrapped in a

, unwrap it. + $pee = preg_replace('!

\s*(]*>)\s*

!', "$1", $pee); + + // In some cases
  • may get wrapped in

    , fix them. + $pee = preg_replace("|

    (|", "$1", $pee); + + // If a

    is wrapped with a

    , move it inside the

    . $pee = preg_replace('|

    ]*)>|i', "

    ", $pee); $pee = str_replace('

    ', '

    ', $pee); + + // If an opening or closing block element tag is preceded by an opening

    tag, remove it. $pee = preg_replace('!

    \s*(]*>)!', "$1", $pee); + + // If an opening or closing block element tag is followed by a closing

    tag, remove it. $pee = preg_replace('!(]*>)\s*

    !', "$1", $pee); - if ($br) { - $pee = preg_replace_callback('/<(script|style).*?<\/\\1>/s', create_function('$matches', 'return str_replace("\n", "", $matches[0]);'), $pee); - $pee = preg_replace('|(?)\s*\n|', "
    \n", $pee); // optionally make line breaks + + // Optionally insert line breaks. + if ( $br ) { + // Replace newlines that shouldn't be touched with a placeholder. + $pee = preg_replace_callback('/<(script|style).*?<\/\\1>/s', '_autop_newline_preservation_helper', $pee); + + // Normalize
    + $pee = str_replace( array( '
    ', '
    ' ), '
    ', $pee ); + + // Replace any new line characters that aren't preceded by a
    with a
    . + $pee = preg_replace('|(?)\s*\n|', "
    \n", $pee); + + // Replace newline placeholders with newlines. $pee = str_replace('', "\n", $pee); } + + // If a
    tag is after an opening or closing block tag, remove it. $pee = preg_replace('!(]*>)\s*
    !', "$1", $pee); + + // If a
    tag is before a subset of opening or closing block tags, remove it. $pee = preg_replace('!
    (\s*]*>)!', '$1', $pee); - if (strpos($pee, ')(.*?)
  • !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 + + // Replace placeholder

     tags with their original content.
    +	if ( !empty($pre_tags) )
    +		$pee = str_replace(array_keys($pre_tags), array_values($pre_tags), $pee);
    +
    +	// Restore newlines in all elements.
    +	if ( false !== strpos( $pee, '' ) ) {
    +		$pee = str_replace( array( '  ', '' ), "\n", $pee );
    +	}
     
     	return $pee;
     }
     
    +/**
    + * Separate HTML elements and comments from the text.
    + *
    + * @since 4.2.4
    + *
    + * @param string $input The text which has to be formatted.
    + * @return array The formatted text.
    + */
    +function wp_html_split( $input ) {
    +	static $regex;
    +
    +	if ( ! isset( $regex ) ) {
    +		$comments =
    +			  '!'           // Start of comment, after the <.
    +			. '(?:'         // Unroll the loop: Consume everything until --> is found.
    +			.     '-(?!->)' // Dash not followed by end of comment.
    +			.     '[^\-]*+' // Consume non-dashes.
    +			. ')*+'         // Loop possessively.
    +			. '(?:-->)?';   // End of comment. If not found, match all input.
    +
    +		$cdata =
    +			  '!\[CDATA\['  // Start of comment, after the <.
    +			. '[^\]]*+'     // Consume non-].
    +			. '(?:'         // Unroll the loop: Consume everything until ]]> is found.
    +			.     '](?!]>)' // One ] not followed by end of comment.
    +			.     '[^\]]*+' // Consume non-].
    +			. ')*+'         // Loop possessively.
    +			. '(?:]]>)?';   // End of comment. If not found, match all input.
    +
    +		$regex =
    +			  '/('              // Capture the entire match.
    +			.     '<'           // Find start of element.
    +			.     '(?(?=!--)'   // Is this a comment?
    +			.         $comments // Find end of comment.
    +			.     '|'
    +			.         '(?(?=!\[CDATA\[)' // Is this a comment?
    +			.             $cdata // Find end of comment.
    +			.         '|'
    +			.             '[^>]*>?' // Find end of element. If not found, match all input.
    +			.         ')'
    +			.     ')'
    +			. ')/s';
    +	}
    +
    +	return preg_split( $regex, $input, -1, PREG_SPLIT_DELIM_CAPTURE );
    +}
     
    -function seems_utf8($Str) { # by bmorel at ssi dot fr
    -	$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
    -		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;
    +/**
    + * Replace characters or phrases within HTML elements only.
    + *
    + * @since 4.2.3
    + *
    + * @param string $haystack The text which has to be formatted.
    + * @param array $replace_pairs In the form array('from' => 'to', ...).
    + * @return string The formatted text.
    + */
    +function wp_replace_in_html_tags( $haystack, $replace_pairs ) {
    +	// Find all elements.
    +	$textarr = wp_html_split( $haystack );
    +	$changed = false;
    +
    +	// Optimize when searching for one item.
    +	if ( 1 === count( $replace_pairs ) ) {
    +		// Extract $needle and $replace.
    +		foreach ( $replace_pairs as $needle => $replace );
    +
    +		// Loop through delimeters (elements) only.
    +		for ( $i = 1, $c = count( $textarr ); $i < $c; $i += 2 ) {
    +			if ( false !== strpos( $textarr[$i], $needle ) ) {
    +				$textarr[$i] = str_replace( $needle, $replace, $textarr[$i] );
    +				$changed = true;
    +			}
    +		}
    +	} else {
    +		// Extract all $needles.
    +		$needles = array_keys( $replace_pairs );
    +
    +		// Loop through delimeters (elements) only.
    +		for ( $i = 1, $c = count( $textarr ); $i < $c; $i += 2 ) {
    +			foreach ( $needles as $needle ) {
    +				if ( false !== strpos( $textarr[$i], $needle ) ) {
    +					$textarr[$i] = strtr( $textarr[$i], $replace_pairs );
    +					$changed = true;
    +					// After one strtr() break out of the foreach loop and look at next element.
    +					break;
    +				}
    +			}
     		}
     	}
    -	return true;
    -}
     
    -function wp_specialchars( $text, $quotes = 0 ) {
    -	// Like htmlspecialchars except don't double-encode HTML entities
    -	$text = str_replace('&&', '&&', $text);
    -	$text = str_replace('&&', '&&', $text);
    -	$text = preg_replace('/&(?:$|([^#])(?![a-z1-4]{1,8};))/', '&$1', $text);
    -	$text = str_replace('<', '<', $text);
    -	$text = str_replace('>', '>', $text);
    -	if ( 'double' === $quotes ) {
    -		$text = str_replace('"', '"', $text);
    -	} elseif ( 'single' === $quotes ) {
    -		$text = str_replace("'", ''', $text);
    -	} elseif ( $quotes ) {
    -		$text = str_replace('"', '"', $text);
    -		$text = str_replace("'", ''', $text);
    +	if ( $changed ) {
    +		$haystack = implode( $textarr );
     	}
    -	return $text;
    +
    +	return $haystack;
     }
     
    -function utf8_uri_encode( $utf8_string, $length = 0 ) {
    -	$unicode = '';
    -	$values = array();
    -	$num_octets = 1;
    -	$unicode_length = 0;
    +/**
    + * Newline preservation help function for wpautop
    + *
    + * @since 3.1.0
    + * @access private
    + *
    + * @param array $matches preg_replace_callback matches array
    + * @return string
    + */
    +function _autop_newline_preservation_helper( $matches ) {
    +	return str_replace( "\n", "", $matches[0] );
    +}
     
    -	$string_length = strlen( $utf8_string );
    -	for ($i = 0; $i < $string_length; $i++ ) {
    +/**
    + * Don't auto-p wrap shortcodes that stand alone
    + *
    + * Ensures that shortcodes are not wrapped in `

    ...

    `. + * + * @since 2.9.0 + * + * @global array $shortcode_tags + * + * @param string $pee The content. + * @return string The filtered content. + */ +function shortcode_unautop( $pee ) { + global $shortcode_tags; - $value = ord( $utf8_string[ $i ] ); + if ( empty( $shortcode_tags ) || !is_array( $shortcode_tags ) ) { + return $pee; + } - if ( $value < 128 ) { - if ( $length && ( $unicode_length >= $length ) ) - break; - $unicode .= chr($value); - $unicode_length++; - } else { - if ( count( $values ) == 0 ) $num_octets = ( $value < 224 ) ? 2 : 3; + $tagregexp = join( '|', array_map( 'preg_quote', array_keys( $shortcode_tags ) ) ); + $spaces = wp_spaces_regexp(); + + $pattern = + '/' + . '

    ' // Opening paragraph + . '(?:' . $spaces . ')*+' // Optional leading whitespace + . '(' // 1: The shortcode + . '\\[' // Opening bracket + . "($tagregexp)" // 2: Shortcode name + . '(?![\\w-])' // Not followed by word character or hyphen + // 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 + . ')?' + . ')' + . ')' + . '(?:' . $spaces . ')*+' // optional trailing whitespace + . '<\\/p>' // closing paragraph + . '/s'; + + return preg_replace( $pattern, '$1', $pee ); +} - $values[] = $value; +/** + * Checks to see if a string is utf8 encoded. + * + * 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. + */ +function seems_utf8( $str ) { + mbstring_binary_safe_encoding(); + $length = strlen($str); + reset_mbstring_encoding(); + for ($i=0; $i < $length; $i++) { + $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; + } + } + return true; +} - if ( $length && ( $unicode_length + ($num_octets * 3) ) > $length ) - break; - if ( count( $values ) == $num_octets ) { - if ($num_octets == 3) { - $unicode .= '%' . dechex($values[0]) . '%' . dechex($values[1]) . '%' . dechex($values[2]); - $unicode_length += 9; - } else { - $unicode .= '%' . dechex($values[0]) . '%' . dechex($values[1]); - $unicode_length += 6; - } +/** + * Converts a number of special characters into their HTML entities. + * + * Specifically deals with: &, <, >, ", and '. + * + * $quote_style can be set to ENT_COMPAT to encode " to + * ", or ENT_QUOTES to do both. Default is ENT_NOQUOTES where no quotes are encoded. + * + * @since 1.2.2 + * @access private + * + * @staticvar string $_charset + * + * @param string $string The text which is to be encoded. + * @param int $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 values; converting single quotes if set to 'single', double if set to 'double' or both if otherwise set. Default is ENT_NOQUOTES. + * @param string $charset Optional. The character encoding of the string. Default is false. + * @param bool $double_encode Optional. Whether 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 ) { + $string = (string) $string; - $values = array(); - $num_octets = 1; - } + if ( 0 === strlen( $string ) ) + return ''; + + // Don't bother if there are no specialchars - saves some processing + 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 ) ) + $quote_style = ENT_NOQUOTES; + 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 ) { + static $_charset = null; + if ( ! isset( $_charset ) ) { + $alloptions = wp_load_alloptions(); + $_charset = isset( $alloptions['blog_charset'] ) ? $alloptions['blog_charset'] : ''; } + $charset = $_charset; } - return $unicode; + if ( in_array( $charset, array( 'utf8', 'utf-8', 'UTF8' ) ) ) + $charset = 'UTF-8'; + + $_quote_style = $quote_style; + + if ( $quote_style === 'double' ) { + $quote_style = ENT_COMPAT; + $_quote_style = ENT_COMPAT; + } elseif ( $quote_style === 'single' ) { + $quote_style = ENT_NOQUOTES; + } + + if ( ! $double_encode ) { + // Guarantee every &entity; is valid, convert &garbage; into &garbage; + // This is required for PHP < 5.4.0 because ENT_HTML401 flag is unavailable. + $string = wp_kses_normalize_entities( $string ); + } + + $string = @htmlspecialchars( $string, $quote_style, $charset, $double_encode ); + + // Backwards compatibility + if ( 'single' === $_quote_style ) + $string = str_replace( "'", ''', $string ); + + return $string; } -function remove_accents($string) { - if ( !preg_match('/[\x80-\xff]/', $string) ) +/** + * Converts a number of HTML entities into their special characters. + * + * Specifically deals with: &, <, >, ", and '. + * + * $quote_style can be set to ENT_COMPAT to decode " entities, + * or ENT_QUOTES to do both " and '. Default is ENT_NOQUOTES where no quotes are decoded. + * + * @since 2.8.0 + * + * @param string $string The text which is to be decoded. + * @param string|int $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 ) { + $string = (string) $string; + + if ( 0 === strlen( $string ) ) { + return ''; + } + + // Don't bother if there are no entities - saves a lot of processing + if ( strpos( $string, '&' ) === false ) { return $string; + } - if (seems_utf8($string)) { - $chars = array( - // Decompositions for Latin-1 Supplement - 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', - chr(195).chr(135) => 'C', chr(195).chr(136) => 'E', - chr(195).chr(137) => 'E', chr(195).chr(138) => 'E', - chr(195).chr(139) => 'E', chr(195).chr(140) => 'I', - chr(195).chr(141) => 'I', chr(195).chr(142) => 'I', - chr(195).chr(143) => 'I', chr(195).chr(145) => 'N', - chr(195).chr(146) => 'O', chr(195).chr(147) => 'O', - chr(195).chr(148) => 'O', chr(195).chr(149) => 'O', - chr(195).chr(150) => 'O', chr(195).chr(153) => 'U', - chr(195).chr(154) => 'U', chr(195).chr(155) => 'U', - chr(195).chr(156) => 'U', chr(195).chr(157) => 'Y', - chr(195).chr(159) => 's', chr(195).chr(160) => 'a', - chr(195).chr(161) => 'a', chr(195).chr(162) => 'a', - chr(195).chr(163) => 'a', chr(195).chr(164) => 'a', - chr(195).chr(165) => 'a', chr(195).chr(167) => 'c', - chr(195).chr(168) => 'e', chr(195).chr(169) => 'e', - chr(195).chr(170) => 'e', chr(195).chr(171) => 'e', - chr(195).chr(172) => 'i', chr(195).chr(173) => 'i', - chr(195).chr(174) => 'i', chr(195).chr(175) => 'i', - 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(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(191) => 'y', + // 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 ) ) { + $quote_style = ENT_QUOTES; + } + + // More complete than get_html_translation_table( HTML_SPECIALCHARS ) + $single = array( ''' => '\'', ''' => '\'' ); + $single_preg = array( '/�*39;/' => ''', '/�*27;/i' => ''' ); + $double = array( '"' => '"', '"' => '"', '"' => '"' ); + $double_preg = array( '/�*34;/' => '"', '/�*22;/i' => '"' ); + $others = array( '<' => '<', '<' => '<', '>' => '>', '>' => '>', '&' => '&', '&' => '&', '&' => '&' ); + $others_preg = array( '/�*60;/' => '<', '/�*62;/' => '>', '/�*38;/' => '&', '/�*26;/i' => '&' ); + + if ( $quote_style === ENT_QUOTES ) { + $translation = array_merge( $single, $double, $others ); + $translation_preg = array_merge( $single_preg, $double_preg, $others_preg ); + } elseif ( $quote_style === ENT_COMPAT || $quote_style === 'double' ) { + $translation = array_merge( $double, $others ); + $translation_preg = array_merge( $double_preg, $others_preg ); + } elseif ( $quote_style === 'single' ) { + $translation = array_merge( $single, $others ); + $translation_preg = array_merge( $single_preg, $others_preg ); + } elseif ( $quote_style === ENT_NOQUOTES ) { + $translation = $others; + $translation_preg = $others_preg; + } + + // Remove zero padding on numeric entities + $string = preg_replace( array_keys( $translation_preg ), array_values( $translation_preg ), $string ); + + // Replace characters according to translation table + return strtr( $string, $translation ); +} + +/** + * Checks for invalid UTF8 in a string. + * + * @since 2.8.0 + * + * @staticvar bool $is_utf8 + * @staticvar bool $utf8_pcre + * + * @param string $string The text which is to be checked. + * @param bool $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 ) { + $string = (string) $string; + + if ( 0 === strlen( $string ) ) { + return ''; + } + + // Store the site charset as a static to avoid multiple calls to get_option() + static $is_utf8 = null; + if ( ! isset( $is_utf8 ) ) { + $is_utf8 = in_array( get_option( 'blog_charset' ), array( 'utf8', 'utf-8', 'UTF8', 'UTF-8' ) ); + } + if ( ! $is_utf8 ) { + return $string; + } + + // Check for support for utf8 in the installed PCRE library once and store the result in a static + static $utf8_pcre = null; + if ( ! isset( $utf8_pcre ) ) { + $utf8_pcre = @preg_match( '/^./u', 'a' ); + } + // We can't demand utf8 in the PCRE installation, so just return the string in those cases + if ( !$utf8_pcre ) { + return $string; + } + + // preg_match fails when it encounters invalid UTF8 in $string + if ( 1 === @preg_match( '/^./us', $string ) ) { + return $string; + } + + // Attempt to strip the bad chars if requested (not recommended) + if ( $strip && function_exists( 'iconv' ) ) { + return iconv( 'utf-8', 'utf-8', $string ); + } + + return ''; +} + +/** + * Encode the Unicode values to be used in the URI. + * + * @since 1.5.0 + * + * @param string $utf8_string + * @param int $length Max length of the string + * @return string String with Unicode encoded for URI. + */ +function utf8_uri_encode( $utf8_string, $length = 0 ) { + $unicode = ''; + $values = array(); + $num_octets = 1; + $unicode_length = 0; + + mbstring_binary_safe_encoding(); + $string_length = strlen( $utf8_string ); + reset_mbstring_encoding(); + + for ($i = 0; $i < $string_length; $i++ ) { + + $value = ord( $utf8_string[ $i ] ); + + if ( $value < 128 ) { + if ( $length && ( $unicode_length >= $length ) ) + break; + $unicode .= chr($value); + $unicode_length++; + } else { + if ( count( $values ) == 0 ) { + if ( $value < 224 ) { + $num_octets = 2; + } elseif ( $value < 240 ) { + $num_octets = 3; + } else { + $num_octets = 4; + } + } + + $values[] = $value; + + if ( $length && ( $unicode_length + ($num_octets * 3) ) > $length ) + break; + if ( count( $values ) == $num_octets ) { + for ( $j = 0; $j < $num_octets; $j++ ) { + $unicode .= '%' . dechex( $values[ $j ] ); + } + + $unicode_length += $num_octets * 3; + + $values = array(); + $num_octets = 1; + } + } + } + + return $unicode; +} + +/** + * Converts all accent characters to ASCII characters. + * + * If there are no accent characters, then the string given is just returned. + * + * @since 1.2.1 + * + * @param string $string Text that might have accent characters + * @return string Filtered string with replaced "nice" characters. + */ +function remove_accents( $string ) { + if ( !preg_match('/[\x80-\xff]/', $string) ) + return $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', + chr(195).chr(134) => 'AE',chr(195).chr(135) => 'C', + chr(195).chr(136) => 'E', chr(195).chr(137) => 'E', + chr(195).chr(138) => 'E', chr(195).chr(139) => 'E', + chr(195).chr(140) => 'I', chr(195).chr(141) => 'I', + chr(195).chr(142) => 'I', chr(195).chr(143) => 'I', + chr(195).chr(144) => 'D', chr(195).chr(145) => 'N', + chr(195).chr(146) => 'O', chr(195).chr(147) => 'O', + chr(195).chr(148) => 'O', chr(195).chr(149) => 'O', + chr(195).chr(150) => 'O', chr(195).chr(153) => 'U', + chr(195).chr(154) => 'U', chr(195).chr(155) => 'U', + chr(195).chr(156) => 'U', chr(195).chr(157) => 'Y', + chr(195).chr(158) => 'TH',chr(195).chr(159) => 's', + chr(195).chr(160) => 'a', chr(195).chr(161) => 'a', + chr(195).chr(162) => 'a', chr(195).chr(163) => 'a', + chr(195).chr(164) => 'a', chr(195).chr(165) => 'a', + chr(195).chr(166) => 'ae',chr(195).chr(167) => 'c', + chr(195).chr(168) => 'e', chr(195).chr(169) => 'e', + chr(195).chr(170) => 'e', chr(195).chr(171) => 'e', + chr(195).chr(172) => 'i', chr(195).chr(173) => 'i', + chr(195).chr(174) => 'i', chr(195).chr(175) => 'i', + 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(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(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', @@ -275,589 +1167,1758 @@ function remove_accents($string) { chr(197).chr(186) => 'z', chr(197).chr(187) => 'Z', chr(197).chr(188) => 'z', chr(197).chr(189) => 'Z', chr(197).chr(190) => 'z', chr(197).chr(191) => 's', + // Decompositions for Latin Extended-B + chr(200).chr(152) => 'S', chr(200).chr(153) => 's', + chr(200).chr(154) => 'T', chr(200).chr(155) => 't', // Euro Sign chr(226).chr(130).chr(172) => 'E', // GBP (Pound) Sign - chr(194).chr(163) => ''); + chr(194).chr(163) => '', + // Vowels with diacritic (Vietnamese) + // unmarked + chr(198).chr(160) => 'O', chr(198).chr(161) => 'o', + chr(198).chr(175) => 'U', chr(198).chr(176) => 'u', + // grave accent + chr(225).chr(186).chr(166) => 'A', chr(225).chr(186).chr(167) => 'a', + chr(225).chr(186).chr(176) => 'A', chr(225).chr(186).chr(177) => 'a', + chr(225).chr(187).chr(128) => 'E', chr(225).chr(187).chr(129) => 'e', + chr(225).chr(187).chr(146) => 'O', chr(225).chr(187).chr(147) => 'o', + chr(225).chr(187).chr(156) => 'O', chr(225).chr(187).chr(157) => 'o', + chr(225).chr(187).chr(170) => 'U', chr(225).chr(187).chr(171) => 'u', + chr(225).chr(187).chr(178) => 'Y', chr(225).chr(187).chr(179) => 'y', + // hook + chr(225).chr(186).chr(162) => 'A', chr(225).chr(186).chr(163) => 'a', + chr(225).chr(186).chr(168) => 'A', chr(225).chr(186).chr(169) => 'a', + chr(225).chr(186).chr(178) => 'A', chr(225).chr(186).chr(179) => 'a', + chr(225).chr(186).chr(186) => 'E', chr(225).chr(186).chr(187) => 'e', + chr(225).chr(187).chr(130) => 'E', chr(225).chr(187).chr(131) => 'e', + chr(225).chr(187).chr(136) => 'I', chr(225).chr(187).chr(137) => 'i', + chr(225).chr(187).chr(142) => 'O', chr(225).chr(187).chr(143) => 'o', + chr(225).chr(187).chr(148) => 'O', chr(225).chr(187).chr(149) => 'o', + chr(225).chr(187).chr(158) => 'O', chr(225).chr(187).chr(159) => 'o', + chr(225).chr(187).chr(166) => 'U', chr(225).chr(187).chr(167) => 'u', + chr(225).chr(187).chr(172) => 'U', chr(225).chr(187).chr(173) => 'u', + chr(225).chr(187).chr(182) => 'Y', chr(225).chr(187).chr(183) => 'y', + // tilde + chr(225).chr(186).chr(170) => 'A', chr(225).chr(186).chr(171) => 'a', + chr(225).chr(186).chr(180) => 'A', chr(225).chr(186).chr(181) => 'a', + chr(225).chr(186).chr(188) => 'E', chr(225).chr(186).chr(189) => 'e', + chr(225).chr(187).chr(132) => 'E', chr(225).chr(187).chr(133) => 'e', + chr(225).chr(187).chr(150) => 'O', chr(225).chr(187).chr(151) => 'o', + chr(225).chr(187).chr(160) => 'O', chr(225).chr(187).chr(161) => 'o', + chr(225).chr(187).chr(174) => 'U', chr(225).chr(187).chr(175) => 'u', + chr(225).chr(187).chr(184) => 'Y', chr(225).chr(187).chr(185) => 'y', + // acute accent + chr(225).chr(186).chr(164) => 'A', chr(225).chr(186).chr(165) => 'a', + chr(225).chr(186).chr(174) => 'A', chr(225).chr(186).chr(175) => 'a', + chr(225).chr(186).chr(190) => 'E', chr(225).chr(186).chr(191) => 'e', + chr(225).chr(187).chr(144) => 'O', chr(225).chr(187).chr(145) => 'o', + chr(225).chr(187).chr(154) => 'O', chr(225).chr(187).chr(155) => 'o', + chr(225).chr(187).chr(168) => 'U', chr(225).chr(187).chr(169) => 'u', + // dot below + chr(225).chr(186).chr(160) => 'A', chr(225).chr(186).chr(161) => 'a', + chr(225).chr(186).chr(172) => 'A', chr(225).chr(186).chr(173) => 'a', + chr(225).chr(186).chr(182) => 'A', chr(225).chr(186).chr(183) => 'a', + chr(225).chr(186).chr(184) => 'E', chr(225).chr(186).chr(185) => 'e', + chr(225).chr(187).chr(134) => 'E', chr(225).chr(187).chr(135) => 'e', + chr(225).chr(187).chr(138) => 'I', chr(225).chr(187).chr(139) => 'i', + chr(225).chr(187).chr(140) => 'O', chr(225).chr(187).chr(141) => 'o', + chr(225).chr(187).chr(152) => 'O', chr(225).chr(187).chr(153) => 'o', + chr(225).chr(187).chr(162) => 'O', chr(225).chr(187).chr(163) => 'o', + chr(225).chr(187).chr(164) => 'U', chr(225).chr(187).chr(165) => 'u', + chr(225).chr(187).chr(176) => 'U', chr(225).chr(187).chr(177) => 'u', + chr(225).chr(187).chr(180) => 'Y', chr(225).chr(187).chr(181) => 'y', + // Vowels with diacritic (Chinese, Hanyu Pinyin) + chr(201).chr(145) => 'a', + // macron + chr(199).chr(149) => 'U', chr(199).chr(150) => 'u', + // acute accent + chr(199).chr(151) => 'U', chr(199).chr(152) => 'u', + // caron + chr(199).chr(141) => 'A', chr(199).chr(142) => 'a', + chr(199).chr(143) => 'I', chr(199).chr(144) => 'i', + chr(199).chr(145) => 'O', chr(199).chr(146) => 'o', + chr(199).chr(147) => 'U', chr(199).chr(148) => 'u', + chr(199).chr(153) => 'U', chr(199).chr(154) => 'u', + // grave accent + chr(199).chr(155) => 'U', chr(199).chr(156) => 'u', + ); + + // Used for locale-specific rules + $locale = get_locale(); + + if ( 'de_DE' == $locale || 'de_DE_formal' == $locale ) { + $chars[ chr(195).chr(132) ] = 'Ae'; + $chars[ chr(195).chr(164) ] = 'ae'; + $chars[ chr(195).chr(150) ] = 'Oe'; + $chars[ chr(195).chr(182) ] = 'oe'; + $chars[ chr(195).chr(156) ] = 'Ue'; + $chars[ chr(195).chr(188) ] = 'ue'; + $chars[ chr(195).chr(159) ] = 'ss'; + } elseif ( 'da_DK' === $locale ) { + $chars[ chr(195).chr(134) ] = 'Ae'; + $chars[ chr(195).chr(166) ] = 'ae'; + $chars[ chr(195).chr(152) ] = 'Oe'; + $chars[ chr(195).chr(184) ] = 'oe'; + $chars[ chr(195).chr(133) ] = 'Aa'; + $chars[ chr(195).chr(165) ] = 'aa'; + } + + $string = strtr($string, $chars); + } else { + $chars = array(); + // Assume ISO-8859-1 if not UTF-8 + $chars['in'] = chr(128).chr(131).chr(138).chr(142).chr(154).chr(158) + .chr(159).chr(162).chr(165).chr(181).chr(192).chr(193).chr(194) + .chr(195).chr(196).chr(197).chr(199).chr(200).chr(201).chr(202) + .chr(203).chr(204).chr(205).chr(206).chr(207).chr(209).chr(210) + .chr(211).chr(212).chr(213).chr(214).chr(216).chr(217).chr(218) + .chr(219).chr(220).chr(221).chr(224).chr(225).chr(226).chr(227) + .chr(228).chr(229).chr(231).chr(232).chr(233).chr(234).chr(235) + .chr(236).chr(237).chr(238).chr(239).chr(241).chr(242).chr(243) + .chr(244).chr(245).chr(246).chr(248).chr(249).chr(250).chr(251) + .chr(252).chr(253).chr(255); + + $chars['out'] = "EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy"; + + $string = strtr($string, $chars['in'], $chars['out']); + $double_chars = array(); + $double_chars['in'] = array(chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254)); + $double_chars['out'] = array('OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th'); + $string = str_replace($double_chars['in'], $double_chars['out'], $string); + } + + return $string; +} + +/** + * Sanitizes a filename, replacing whitespace with dashes. + * + * 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. Trims period, dash and underscore from beginning + * and end of filename. + * + * @since 2.1.0 + * + * @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)); + /** + * Filter the list of characters to remove from a filename. + * + * @since 2.8.0 + * + * @param array $special_chars Characters to remove. + * @param string $filename_raw Filename as it was passed into sanitize_file_name(). + */ + $special_chars = apply_filters( 'sanitize_file_name_chars', $special_chars, $filename_raw ); + $filename = preg_replace( "#\x{00a0}#siu", ' ', $filename ); + $filename = str_replace( $special_chars, '', $filename ); + $filename = str_replace( array( '%20', '+' ), '-', $filename ); + $filename = preg_replace( '/[\r\n\t -]+/', '-', $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 ) { + /** + * Filter a sanitized filename string. + * + * @since 2.8.0 + * + * @param string $filename Sanitized filename. + * @param string $filename_raw The filename prior to sanitization. + */ + 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. Postfix 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; + /** This filter is documented in wp-includes/formatting.php */ + return apply_filters('sanitize_file_name', $filename, $filename_raw); +} + +/** + * Sanitizes a username, stripping out unsafe characters. + * + * Removes tags, octets, entities, and if strict is enabled, will only keep + * alphanumeric, _, space, ., -, @. After sanitizing, it passes the username, + * raw username (the username in the parameter), and the value of $strict as + * parameters for the 'sanitize_user' filter. + * + * @since 2.0.0 + * + * @param string $username The username to be sanitized. + * @param bool $strict If set limits $username to specific characters. Default false. + * @return string The sanitized username, after passing through filters. + */ +function sanitize_user( $username, $strict = false ) { + $raw_username = $username; + $username = wp_strip_all_tags( $username ); + $username = remove_accents( $username ); + // Kill octets + $username = preg_replace( '|%([a-fA-F0-9][a-fA-F0-9])|', '', $username ); + $username = preg_replace( '/&.+?;/', '', $username ); // Kill entities + + // If strict, reduce to ASCII for max portability. + if ( $strict ) + $username = preg_replace( '|[^a-z0-9 _.\-@]|i', '', $username ); + + $username = trim( $username ); + // Consolidate contiguous whitespace + $username = preg_replace( '|\s+|', ' ', $username ); + + /** + * Filter a sanitized username string. + * + * @since 2.0.1 + * + * @param string $username Sanitized username. + * @param string $raw_username The username prior to sanitization. + * @param bool $strict Whether to limit the sanitization to specific characters. Default false. + */ + return apply_filters( 'sanitize_user', $username, $raw_username, $strict ); +} + +/** + * Sanitizes a string key. + * + * Keys are used as internal identifiers. Lowercase alphanumeric characters, dashes and underscores are allowed. + * + * @since 3.0.0 + * + * @param string $key String key + * @return string Sanitized key + */ +function sanitize_key( $key ) { + $raw_key = $key; + $key = strtolower( $key ); + $key = preg_replace( '/[^a-z0-9_\-]/', '', $key ); + + /** + * Filter a sanitized key string. + * + * @since 3.0.0 + * + * @param string $key Sanitized key. + * @param string $raw_key The key prior to sanitization. + */ + return apply_filters( 'sanitize_key', $key, $raw_key ); +} + +/** + * Sanitizes a title, or returns a fallback title. + * + * Specifically, HTML and PHP tags are stripped. Further actions can be added + * via the plugin API. If $title is empty and $fallback_title is set, the latter + * will be used. + * + * @since 1.0.0 + * + * @param string $title The string to be sanitized. + * @param string $fallback_title Optional. A title to use if $title is empty. + * @param string $context Optional. The operation for which the string is sanitized + * @return string The sanitized string. + */ +function sanitize_title( $title, $fallback_title = '', $context = 'save' ) { + $raw_title = $title; + + if ( 'save' == $context ) + $title = remove_accents($title); + + /** + * Filter a sanitized title string. + * + * @since 1.2.0 + * + * @param string $title Sanitized title. + * @param string $raw_title The title prior to sanitization. + * @param string $context The context for which the title is being sanitized. + */ + $title = apply_filters( 'sanitize_title', $title, $raw_title, $context ); + + if ( '' === $title || false === $title ) + $title = $fallback_title; + + return $title; +} + +/** + * Sanitizes a title with the 'query' context. + * + * Used for querying the database for a value from URL. + * + * @since 3.1.0 + * + * @param string $title The string to be sanitized. + * @return string The sanitized string. + */ +function sanitize_title_for_query( $title ) { + return sanitize_title( $title, '', 'query' ); +} + +/** + * Sanitizes a title, replacing whitespace and a few other characters with dashes. + * + * Limits the output to alphanumeric characters, underscore (_) and dash (-). + * Whitespace becomes a dash. + * + * @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, $raw_title = '', $context = 'display' ) { + $title = strip_tags($title); + // Preserve escaped octets. + $title = preg_replace('|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title); + // Remove percent signs that are not part of an octet. + $title = str_replace('%', '', $title); + // Restore octets. + $title = preg_replace('|---([a-fA-F0-9][a-fA-F0-9])---|', '%$1', $title); + + if (seems_utf8($title)) { + if (function_exists('mb_strtolower')) { + $title = mb_strtolower($title, 'UTF-8'); + } + $title = utf8_uri_encode($title, 200); + } + + $title = strtolower($title); + $title = preg_replace('/&.+?;/', '', $title); // kill entities + $title = str_replace('.', '-', $title); + + if ( 'save' == $context ) { + // Convert nbsp, ndash and mdash to hyphens + $title = str_replace( array( '%c2%a0', '%e2%80%93', '%e2%80%94' ), '-', $title ); + + // Strip these characters entirely + $title = str_replace( array( + // iexcl and iquest + '%c2%a1', '%c2%bf', + // angle quotes + '%c2%ab', '%c2%bb', '%e2%80%b9', '%e2%80%ba', + // curly quotes + '%e2%80%98', '%e2%80%99', '%e2%80%9c', '%e2%80%9d', + '%e2%80%9a', '%e2%80%9b', '%e2%80%9e', '%e2%80%9f', + // copy, reg, deg, hellip and trade + '%c2%a9', '%c2%ae', '%c2%b0', '%e2%80%a6', '%e2%84%a2', + // acute accents + '%c2%b4', '%cb%8a', '%cc%81', '%cd%81', + // grave accent, macron, caron + '%cc%80', '%cc%84', '%cc%8c', + ), '', $title ); + + // Convert times to x + $title = str_replace( '%c3%97', 'x', $title ); + } + + $title = preg_replace('/[^%a-z0-9 _-]/', '', $title); + $title = preg_replace('/\s+/', '-', $title); + $title = preg_replace('|-+|', '-', $title); + $title = trim($title, '-'); + + return $title; +} + +/** + * Ensures a string is a valid SQL 'order by' clause. + * + * Accepts one or more columns, with or without a sort order (ASC / DESC). + * e.g. 'column_1', 'column_1, column_2', 'column_1 ASC, column_2 DESC' etc. + * + * Also accepts 'RAND()'. + * + * @since 2.5.1 + * + * @param string $orderby Order by clause to be validated. + * @return string|false Returns $orderby if valid, false otherwise. + */ +function sanitize_sql_orderby( $orderby ) { + if ( preg_match( '/^\s*(([a-z0-9_]+|`[a-z0-9_]+`)(\s+(ASC|DESC))?\s*(,\s*(?=[a-z0-9_`])|$))+$/i', $orderby ) || preg_match( '/^\s*RAND\(\s*\)\s*$/i', $orderby ) ) { + return $orderby; + } + return false; +} + +/** + * Sanitizes an 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 Optional. The value to return if the sanitization ends up as an empty string. + * Defaults to 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; + + /** + * Filter a sanitized HTML class string. + * + * @since 2.8.0 + * + * @param string $sanitized The sanitized HTML class. + * @param string $class HTML class before sanitization. + * @param string $fallback The fallback string. + */ + return apply_filters( 'sanitize_html_class', $sanitized, $class, $fallback ); +} + +/** + * Converts lone & characters into `&` (a.k.a. `&`) + * + * @since 0.71 + * + * @param string $content String of characters to be converted. + * @param string $deprecated Not used. + * @return string Converted string. + */ +function convert_chars( $content, $deprecated = '' ) { + if ( ! empty( $deprecated ) ) { + _deprecated_argument( __FUNCTION__, '0.71' ); + } + + if ( strpos( $content, '&' ) !== false ) { + $content = preg_replace( '/&([^#])(?![a-z1-4]{1,8};)/i', '&$1', $content ); + } + + return $content; +} + +/** + * Converts invalid Unicode references range to valid range. + * + * @since 4.3.0 + * + * @param string $content String with entities that need converting. + * @return string Converted string. + */ +function convert_invalid_entities( $content ) { + $wp_htmltranswinuni = array( + '€' => '€', // the Euro sign + '' => '', + '‚' => '‚', // these are Windows CP1252 specific characters + 'ƒ' => 'ƒ', // they would look weird on non-Windows browsers + '„' => '„', + '…' => '…', + '†' => '†', + '‡' => '‡', + 'ˆ' => 'ˆ', + '‰' => '‰', + 'Š' => 'Š', + '‹' => '‹', + 'Œ' => 'Œ', + '' => '', + 'Ž' => 'Ž', + '' => '', + '' => '', + '‘' => '‘', + '’' => '’', + '“' => '“', + '”' => '”', + '•' => '•', + '–' => '–', + '—' => '—', + '˜' => '˜', + '™' => '™', + 'š' => 'š', + '›' => '›', + 'œ' => 'œ', + '' => '', + 'ž' => 'ž', + 'Ÿ' => 'Ÿ' + ); + + if ( strpos( $content, '' ) !== false ) { + $content = strtr( $content, $wp_htmltranswinuni ); + } + + return $content; +} + +/** + * Balances tags if forced to, or if the 'use_balanceTags' option is set to true. + * + * @since 0.71 + * + * @param string $text Text to be balanced + * @param bool $force If true, forces balancing, ignoring the value of the option. Default false. + * @return string Balanced text + */ +function balanceTags( $text, $force = false ) { + if ( $force || get_option('use_balanceTags') == 1 ) { + return force_balance_tags( $text ); + } else { + return $text; + } +} + +/** + * Balances tags of string using a modified stack. + * + * @since 2.0.4 + * + * @author Leonard Lin + * @license GPL + * @copyright November 4, 2001 + * @version 1.1 + * @todo Make better - change loop condition to $text in 1.2 + * @internal Modified by Scott Reilly (coffee2code) 02 Aug 2004 + * 1.1 Fixed handling of append/stack pop order of end text + * Added Cleaning Hooks + * 1.0 First Version + * + * @param string $text Text to be balanced. + * @return string Balanced text. + */ +function force_balance_tags( $text ) { + $tagstack = array(); + $stacksize = 0; + $tagqueue = ''; + $newtext = ''; + // Known single-entity/self-closing tags + $single_tags = array( 'area', 'base', 'basefont', 'br', 'col', 'command', 'embed', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param', 'source' ); + // Tags that can be immediately nested within themselves + $nestable_tags = array( 'blockquote', 'div', 'object', 'q', 'span' ); + + // WP bug fix for comments - in case you REALLY meant to type '< !--' + $text = str_replace('< !--', '< !--', $text); + // WP bug fix for LOVE <3 (and other situations with '<' before a number) + $text = preg_replace('#<([0-9]{1})#', '<$1', $text); + + while ( preg_match("/<(\/?[\w:]*)\s*([^>]*)>/", $text, $regex) ) { + $newtext .= $tagqueue; + + $i = strpos($text, $regex[0]); + $l = strlen($regex[0]); + + // clear the shifter + $tagqueue = ''; + // Pop or Push + if ( isset($regex[1][0]) && '/' == $regex[1][0] ) { // End Tag + $tag = strtolower(substr($regex[1],1)); + // if too many closing tags + if ( $stacksize <= 0 ) { + $tag = ''; + // or close to be safe $tag = '/' . $tag; + } + // if stacktop value = tag close value then pop + elseif ( $tagstack[$stacksize - 1] == $tag ) { // found closing tag + $tag = ''; // Close Tag + // Pop + array_pop( $tagstack ); + $stacksize--; + } else { // closing tag not at top, search for it + for ( $j = $stacksize-1; $j >= 0; $j-- ) { + if ( $tagstack[$j] == $tag ) { + // add tag to tagqueue + for ( $k = $stacksize-1; $k >= $j; $k--) { + $tagqueue .= ''; + $stacksize--; + } + break; + } + } + $tag = ''; + } + } else { // Begin Tag + $tag = strtolower($regex[1]); + + // Tag Cleaning + + // If it's an empty tag "< >", do nothing + if ( '' == $tag ) { + // do nothing + } + // ElseIf it presents itself as a self-closing tag... + elseif ( substr( $regex[2], -1 ) == '/' ) { + // ...but it isn't a known single-entity self-closing tag, then don't let it be treated as such and + // immediately close it with a closing tag (the tag will encapsulate no text as a result) + if ( ! in_array( $tag, $single_tags ) ) + $regex[2] = trim( substr( $regex[2], 0, -1 ) ) . "> 0 && !in_array($tag, $nestable_tags) && $tagstack[$stacksize - 1] == $tag ) { + $tagqueue = ''; + $stacksize--; + } + $stacksize = array_push( $tagstack, $tag ); + } + + // Attributes + $attributes = $regex[2]; + if ( ! empty( $attributes ) && $attributes[0] != '>' ) + $attributes = ' ' . $attributes; + + $tag = '<' . $tag . $attributes . '>'; + //If already queuing a close tag, then put this tag on, too + if ( !empty($tagqueue) ) { + $tagqueue .= $tag; + $tag = ''; + } + } + $newtext .= substr($text, 0, $i) . $tag; + $text = substr($text, $i + $l); + } + + // Clear Tag Queue + $newtext .= $tagqueue; + + // Add Remaining text + $newtext .= $text; + + // Empty Stack + while( $x = array_pop($tagstack) ) + $newtext .= ''; // Add remaining tags to close + + // WP fix for the bug with HTML comments + $newtext = str_replace("< !--","