PageRenderTime 238ms CodeModel.GetById 80ms app.highlight 106ms RepoModel.GetById 15ms app.codeStats 2ms

/wp-includes/formatting.php

https://github.com/markjaquith/WordPress
PHP | 4802 lines | 2808 code | 430 blank | 1564 comment | 427 complexity | 62e82ef251b0bb1c3aada48aa8ef8067 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2/**
   3 * Main WordPress Formatting API.
   4 *
   5 * Handles many functions for formatting output.
   6 *
   7 * @package WordPress
   8 */
   9
  10/**
  11 * Replaces common plain text characters into formatted entities
  12 *
  13 * As an example,
  14 *
  15 *     'cause today's effort makes it worth tomorrow's "holiday" ...
  16 *
  17 * Becomes:
  18 *
  19 *     &#8217;cause today&#8217;s effort makes it worth tomorrow&#8217;s &#8220;holiday&#8221; &#8230;
  20 *
  21 * Code within certain html blocks are skipped.
  22 *
  23 * Do not use this function before the 'init' action hook; everything will break.
  24 *
  25 * @since 0.71
  26 *
  27 * @global array $wp_cockneyreplace Array of formatted entities for certain common phrases
  28 * @global array $shortcode_tags
  29 * @staticvar array $static_characters
  30 * @staticvar array $static_replacements
  31 * @staticvar array $dynamic_characters
  32 * @staticvar array $dynamic_replacements
  33 * @staticvar array $default_no_texturize_tags
  34 * @staticvar array $default_no_texturize_shortcodes
  35 * @staticvar bool  $run_texturize
  36 *
  37 * @param string $text The text to be formatted
  38 * @param bool   $reset Set to true for unit testing. Translated patterns will reset.
  39 * @return string The string replaced with html entities
  40 */
  41function wptexturize( $text, $reset = false ) {
  42	global $wp_cockneyreplace, $shortcode_tags;
  43	static $static_characters = null,
  44		$static_replacements = null,
  45		$dynamic_characters = null,
  46		$dynamic_replacements = null,
  47		$default_no_texturize_tags = null,
  48		$default_no_texturize_shortcodes = null,
  49		$run_texturize = true,
  50		$apos = null,
  51		$prime = null,
  52		$double_prime = null,
  53		$opening_quote = null,
  54		$closing_quote = null,
  55		$opening_single_quote = null,
  56		$closing_single_quote = null,
  57		$open_q_flag = '<!--oq-->',
  58		$open_sq_flag = '<!--osq-->',
  59		$apos_flag = '<!--apos-->';
  60
  61	// If there's nothing to do, just stop.
  62	if ( empty( $text ) || false === $run_texturize ) {
  63		return $text;
  64	}
  65
  66	// Set up static variables. Run once only.
  67	if ( $reset || ! isset( $static_characters ) ) {
  68		/**
  69		 * Filter whether to skip running wptexturize().
  70		 *
  71		 * Passing false to the filter will effectively short-circuit wptexturize().
  72		 * returning the original text passed to the function instead.
  73		 *
  74		 * The filter runs only once, the first time wptexturize() is called.
  75		 *
  76		 * @since 4.0.0
  77		 *
  78		 * @see wptexturize()
  79		 *
  80		 * @param bool $run_texturize Whether to short-circuit wptexturize().
  81		 */
  82		$run_texturize = apply_filters( 'run_wptexturize', $run_texturize );
  83		if ( false === $run_texturize ) {
  84			return $text;
  85		}
  86
  87		/* translators: opening curly double quote */
  88		$opening_quote = _x( '&#8220;', 'opening curly double quote' );
  89		/* translators: closing curly double quote */
  90		$closing_quote = _x( '&#8221;', 'closing curly double quote' );
  91
  92		/* translators: apostrophe, for example in 'cause or can't */
  93		$apos = _x( '&#8217;', 'apostrophe' );
  94
  95		/* translators: prime, for example in 9' (nine feet) */
  96		$prime = _x( '&#8242;', 'prime' );
  97		/* translators: double prime, for example in 9" (nine inches) */
  98		$double_prime = _x( '&#8243;', 'double prime' );
  99
 100		/* translators: opening curly single quote */
 101		$opening_single_quote = _x( '&#8216;', 'opening curly single quote' );
 102		/* translators: closing curly single quote */
 103		$closing_single_quote = _x( '&#8217;', 'closing curly single quote' );
 104
 105		/* translators: en dash */
 106		$en_dash = _x( '&#8211;', 'en dash' );
 107		/* translators: em dash */
 108		$em_dash = _x( '&#8212;', 'em dash' );
 109
 110		$default_no_texturize_tags = array('pre', 'code', 'kbd', 'style', 'script', 'tt');
 111		$default_no_texturize_shortcodes = array('code');
 112
 113		// if a plugin has provided an autocorrect array, use it
 114		if ( isset($wp_cockneyreplace) ) {
 115			$cockney = array_keys( $wp_cockneyreplace );
 116			$cockneyreplace = array_values( $wp_cockneyreplace );
 117		} else {
 118			/* translators: This is a comma-separated list of words that defy the syntax of quotations in normal use,
 119			 * for example...  'We do not have enough words yet' ... is a typical quoted phrase.  But when we write
 120			 * lines of code 'til we have enough of 'em, then we need to insert apostrophes instead of quotes.
 121			 */
 122			$cockney = explode( ',', _x( "'tain't,'twere,'twas,'tis,'twill,'til,'bout,'nuff,'round,'cause,'em",
 123				'Comma-separated list of words to texturize in your language' ) );
 124
 125			$cockneyreplace = explode( ',', _x( '&#8217;tain&#8217;t,&#8217;twere,&#8217;twas,&#8217;tis,&#8217;twill,&#8217;til,&#8217;bout,&#8217;nuff,&#8217;round,&#8217;cause,&#8217;em',
 126				'Comma-separated list of replacement words in your language' ) );
 127		}
 128
 129		$static_characters = array_merge( array( '...', '``', '\'\'', ' (tm)' ), $cockney );
 130		$static_replacements = array_merge( array( '&#8230;', $opening_quote, $closing_quote, ' &#8482;' ), $cockneyreplace );
 131
 132
 133		// Pattern-based replacements of characters.
 134		// Sort the remaining patterns into several arrays for performance tuning.
 135		$dynamic_characters = array( 'apos' => array(), 'quote' => array(), 'dash' => array() );
 136		$dynamic_replacements = array( 'apos' => array(), 'quote' => array(), 'dash' => array() );
 137		$dynamic = array();
 138		$spaces = wp_spaces_regexp();
 139
 140		// '99' and '99" are ambiguous among other patterns; assume it's an abbreviated year at the end of a quotation.
 141		if ( "'" !== $apos || "'" !== $closing_single_quote ) {
 142			$dynamic[ '/\'(\d\d)\'(?=\Z|[.,:;!?)}\-\]]|&gt;|' . $spaces . ')/' ] = $apos_flag . '$1' . $closing_single_quote;
 143		}
 144		if ( "'" !== $apos || '"' !== $closing_quote ) {
 145			$dynamic[ '/\'(\d\d)"(?=\Z|[.,:;!?)}\-\]]|&gt;|' . $spaces . ')/' ] = $apos_flag . '$1' . $closing_quote;
 146		}
 147
 148		// '99 '99s '99's (apostrophe)  But never '9 or '99% or '999 or '99.0.
 149		if ( "'" !== $apos ) {
 150			$dynamic[ '/\'(?=\d\d(?:\Z|(?![%\d]|[.,]\d)))/' ] = $apos_flag;
 151		}
 152
 153		// Quoted Numbers like '0.42'
 154		if ( "'" !== $opening_single_quote && "'" !== $closing_single_quote ) {
 155			$dynamic[ '/(?<=\A|' . $spaces . ')\'(\d[.,\d]*)\'/' ] = $open_sq_flag . '$1' . $closing_single_quote;
 156		}
 157
 158		// Single quote at start, or preceded by (, {, <, [, ", -, or spaces.
 159		if ( "'" !== $opening_single_quote ) {
 160			$dynamic[ '/(?<=\A|[([{"\-]|&lt;|' . $spaces . ')\'/' ] = $open_sq_flag;
 161		}
 162
 163		// Apostrophe in a word.  No spaces, double apostrophes, or other punctuation.
 164		if ( "'" !== $apos ) {
 165			$dynamic[ '/(?<!' . $spaces . ')\'(?!\Z|[.,:;!?"\'(){}[\]\-]|&[lg]t;|' . $spaces . ')/' ] = $apos_flag;
 166		}
 167
 168		$dynamic_characters['apos'] = array_keys( $dynamic );
 169		$dynamic_replacements['apos'] = array_values( $dynamic );
 170		$dynamic = array();
 171
 172		// Quoted Numbers like "42"
 173		if ( '"' !== $opening_quote && '"' !== $closing_quote ) {
 174			$dynamic[ '/(?<=\A|' . $spaces . ')"(\d[.,\d]*)"/' ] = $open_q_flag . '$1' . $closing_quote;
 175		}
 176
 177		// Double quote at start, or preceded by (, {, <, [, -, or spaces, and not followed by spaces.
 178		if ( '"' !== $opening_quote ) {
 179			$dynamic[ '/(?<=\A|[([{\-]|&lt;|' . $spaces . ')"(?!' . $spaces . ')/' ] = $open_q_flag;
 180		}
 181
 182		$dynamic_characters['quote'] = array_keys( $dynamic );
 183		$dynamic_replacements['quote'] = array_values( $dynamic );
 184		$dynamic = array();
 185
 186		// Dashes and spaces
 187		$dynamic[ '/---/' ] = $em_dash;
 188		$dynamic[ '/(?<=^|' . $spaces . ')--(?=$|' . $spaces . ')/' ] = $em_dash;
 189		$dynamic[ '/(?<!xn)--/' ] = $en_dash;
 190		$dynamic[ '/(?<=^|' . $spaces . ')-(?=$|' . $spaces . ')/' ] = $en_dash;
 191
 192		$dynamic_characters['dash'] = array_keys( $dynamic );
 193		$dynamic_replacements['dash'] = array_values( $dynamic );
 194	}
 195
 196	// Must do this every time in case plugins use these filters in a context sensitive manner
 197	/**
 198	 * Filter the list of HTML elements not to texturize.
 199	 *
 200	 * @since 2.8.0
 201	 *
 202	 * @param array $default_no_texturize_tags An array of HTML element names.
 203	 */
 204	$no_texturize_tags = apply_filters( 'no_texturize_tags', $default_no_texturize_tags );
 205	/**
 206	 * Filter the list of shortcodes not to texturize.
 207	 *
 208	 * @since 2.8.0
 209	 *
 210	 * @param array $default_no_texturize_shortcodes An array of shortcode names.
 211	 */
 212	$no_texturize_shortcodes = apply_filters( 'no_texturize_shortcodes', $default_no_texturize_shortcodes );
 213
 214	$no_texturize_tags_stack = array();
 215	$no_texturize_shortcodes_stack = array();
 216
 217	// Look for shortcodes and HTML elements.
 218
 219	preg_match_all( '@\[/?([^<>&/\[\]\x00-\x20=]++)@', $text, $matches );
 220	$tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] );
 221	$found_shortcodes = ! empty( $tagnames );
 222	$shortcode_regex = $found_shortcodes ? _get_wptexturize_shortcode_regex( $tagnames ) : '';
 223	$regex = _get_wptexturize_split_regex( $shortcode_regex );
 224
 225	$textarr = preg_split( $regex, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
 226
 227	foreach ( $textarr as &$curl ) {
 228		// Only call _wptexturize_pushpop_element if $curl is a delimiter.
 229		$first = $curl[0];
 230		if ( '<' === $first ) {
 231			if ( '<!--' === substr( $curl, 0, 4 ) ) {
 232				// This is an HTML comment delimiter.
 233				continue;
 234			} else {
 235				// This is an HTML element delimiter.
 236
 237				// Replace each & with &#038; unless it already looks like an entity.
 238				$curl = preg_replace( '/&(?!#(?:\d+|x[a-f0-9]+);|[a-z1-4]{1,8};)/i', '&#038;', $curl );
 239
 240				_wptexturize_pushpop_element( $curl, $no_texturize_tags_stack, $no_texturize_tags );
 241			}
 242
 243		} elseif ( '' === trim( $curl ) ) {
 244			// This is a newline between delimiters.  Performance improves when we check this.
 245			continue;
 246
 247		} elseif ( '[' === $first && $found_shortcodes && 1 === preg_match( '/^' . $shortcode_regex . '$/', $curl ) ) {
 248			// This is a shortcode delimiter.
 249
 250			if ( '[[' !== substr( $curl, 0, 2 ) && ']]' !== substr( $curl, -2 ) ) {
 251				// Looks like a normal shortcode.
 252				_wptexturize_pushpop_element( $curl, $no_texturize_shortcodes_stack, $no_texturize_shortcodes );
 253			} else {
 254				// Looks like an escaped shortcode.
 255				continue;
 256			}
 257
 258		} elseif ( empty( $no_texturize_shortcodes_stack ) && empty( $no_texturize_tags_stack ) ) {
 259			// This is neither a delimiter, nor is this content inside of no_texturize pairs.  Do texturize.
 260
 261			$curl = str_replace( $static_characters, $static_replacements, $curl );
 262
 263			if ( false !== strpos( $curl, "'" ) ) {
 264				$curl = preg_replace( $dynamic_characters['apos'], $dynamic_replacements['apos'], $curl );
 265				$curl = wptexturize_primes( $curl, "'", $prime, $open_sq_flag, $closing_single_quote );
 266				$curl = str_replace( $apos_flag, $apos, $curl );
 267				$curl = str_replace( $open_sq_flag, $opening_single_quote, $curl );
 268			}
 269			if ( false !== strpos( $curl, '"' ) ) {
 270				$curl = preg_replace( $dynamic_characters['quote'], $dynamic_replacements['quote'], $curl );
 271				$curl = wptexturize_primes( $curl, '"', $double_prime, $open_q_flag, $closing_quote );
 272				$curl = str_replace( $open_q_flag, $opening_quote, $curl );
 273			}
 274			if ( false !== strpos( $curl, '-' ) ) {
 275				$curl = preg_replace( $dynamic_characters['dash'], $dynamic_replacements['dash'], $curl );
 276			}
 277
 278			// 9x9 (times), but never 0x9999
 279			if ( 1 === preg_match( '/(?<=\d)x\d/', $curl ) ) {
 280				// Searching for a digit is 10 times more expensive than for the x, so we avoid doing this one!
 281				$curl = preg_replace( '/\b(\d(?(?<=0)[\d\.,]+|[\d\.,]*))x(\d[\d\.,]*)\b/', '$1&#215;$2', $curl );
 282			}
 283
 284			// Replace each & with &#038; unless it already looks like an entity.
 285			$curl = preg_replace( '/&(?!#(?:\d+|x[a-f0-9]+);|[a-z1-4]{1,8};)/i', '&#038;', $curl );
 286		}
 287	}
 288
 289	return implode( '', $textarr );
 290}
 291
 292/**
 293 * Implements a logic tree to determine whether or not "7'." represents seven feet,
 294 * then converts the special char into either a prime char or a closing quote char.
 295 *
 296 * @since 4.3.0
 297 *
 298 * @param string $haystack    The plain text to be searched.
 299 * @param string $needle      The character to search for such as ' or ".
 300 * @param string $prime       The prime char to use for replacement.
 301 * @param string $open_quote  The opening quote char. Opening quote replacement must be
 302 *                            accomplished already.
 303 * @param string $close_quote The closing quote char to use for replacement.
 304 * @return string The $haystack value after primes and quotes replacements.
 305 */
 306function wptexturize_primes( $haystack, $needle, $prime, $open_quote, $close_quote ) {
 307	$spaces = wp_spaces_regexp();
 308	$flag = '<!--wp-prime-or-quote-->';
 309	$quote_pattern = "/$needle(?=\\Z|[.,:;!?)}\\-\\]]|&gt;|" . $spaces . ")/";
 310	$prime_pattern    = "/(?<=\\d)$needle/";
 311	$flag_after_digit = "/(?<=\\d)$flag/";
 312	$flag_no_digit    = "/(?<!\\d)$flag/";
 313
 314	$sentences = explode( $open_quote, $haystack );
 315
 316	foreach ( $sentences as $key => &$sentence ) {
 317		if ( false === strpos( $sentence, $needle ) ) {
 318			continue;
 319		} elseif ( 0 !== $key && 0 === substr_count( $sentence, $close_quote ) ) {
 320			$sentence = preg_replace( $quote_pattern, $flag, $sentence, -1, $count );
 321			if ( $count > 1 ) {
 322				// This sentence appears to have multiple closing quotes.  Attempt Vulcan logic.
 323				$sentence = preg_replace( $flag_no_digit, $close_quote, $sentence, -1, $count2 );
 324				if ( 0 === $count2 ) {
 325					// Try looking for a quote followed by a period.
 326					$count2 = substr_count( $sentence, "$flag." );
 327					if ( $count2 > 0 ) {
 328						// Assume the rightmost quote-period match is the end of quotation.
 329						$pos = strrpos( $sentence, "$flag." );
 330					} else {
 331						// When all else fails, make the rightmost candidate a closing quote.
 332						// This is most likely to be problematic in the context of bug #18549.
 333						$pos = strrpos( $sentence, $flag );
 334					}
 335					$sentence = substr_replace( $sentence, $close_quote, $pos, strlen( $flag ) );
 336				}
 337				// Use conventional replacement on any remaining primes and quotes.
 338				$sentence = preg_replace( $prime_pattern, $prime, $sentence );
 339				$sentence = preg_replace( $flag_after_digit, $prime, $sentence );
 340				$sentence = str_replace( $flag, $close_quote, $sentence );
 341			} elseif ( 1 == $count ) {
 342				// Found only one closing quote candidate, so give it priority over primes.
 343				$sentence = str_replace( $flag, $close_quote, $sentence );
 344				$sentence = preg_replace( $prime_pattern, $prime, $sentence );
 345			} else {
 346				// No closing quotes found.  Just run primes pattern.
 347				$sentence = preg_replace( $prime_pattern, $prime, $sentence );
 348			}
 349		} else {
 350			$sentence = preg_replace( $prime_pattern, $prime, $sentence );
 351			$sentence = preg_replace( $quote_pattern, $close_quote, $sentence );
 352		}
 353		if ( '"' == $needle && false !== strpos( $sentence, '"' ) ) {
 354			$sentence = str_replace( '"', $close_quote, $sentence );
 355		}
 356	}
 357
 358	return implode( $open_quote, $sentences );
 359}
 360
 361/**
 362 * Search for disabled element tags. Push element to stack on tag open and pop
 363 * on tag close.
 364 *
 365 * Assumes first char of $text is tag opening and last char is tag closing.
 366 * Assumes second char of $text is optionally '/' to indicate closing as in </html>.
 367 *
 368 * @since 2.9.0
 369 * @access private
 370 *
 371 * @param string $text Text to check. Must be a tag like `<html>` or `[shortcode]`.
 372 * @param array  $stack List of open tag elements.
 373 * @param array  $disabled_elements The tag names to match against. Spaces are not allowed in tag names.
 374 */
 375function _wptexturize_pushpop_element( $text, &$stack, $disabled_elements ) {
 376	// Is it an opening tag or closing tag?
 377	if ( '/' !== $text[1] ) {
 378		$opening_tag = true;
 379		$name_offset = 1;
 380	} elseif ( 0 == count( $stack ) ) {
 381		// Stack is empty. Just stop.
 382		return;
 383	} else {
 384		$opening_tag = false;
 385		$name_offset = 2;
 386	}
 387
 388	// Parse out the tag name.
 389	$space = strpos( $text, ' ' );
 390	if ( false === $space ) {
 391		$space = -1;
 392	} else {
 393		$space -= $name_offset;
 394	}
 395	$tag = substr( $text, $name_offset, $space );
 396
 397	// Handle disabled tags.
 398	if ( in_array( $tag, $disabled_elements ) ) {
 399		if ( $opening_tag ) {
 400			/*
 401			 * This disables texturize until we find a closing tag of our type
 402			 * (e.g. <pre>) even if there was invalid nesting before that
 403			 *
 404			 * Example: in the case <pre>sadsadasd</code>"baba"</pre>
 405			 *          "baba" won't be texturize
 406			 */
 407
 408			array_push( $stack, $tag );
 409		} elseif ( end( $stack ) == $tag ) {
 410			array_pop( $stack );
 411		}
 412	}
 413}
 414
 415/**
 416 * Replaces double line-breaks with paragraph elements.
 417 *
 418 * A group of regex replaces used to identify text formatted with newlines and
 419 * replace double line-breaks with HTML paragraph tags. The remaining line-breaks
 420 * after conversion become <<br />> tags, unless $br is set to '0' or 'false'.
 421 *
 422 * @since 0.71
 423 *
 424 * @param string $pee The text which has to be formatted.
 425 * @param bool   $br  Optional. If set, this will convert all remaining line-breaks
 426 *                    after paragraphing. Default true.
 427 * @return string Text which has been converted into correct paragraph tags.
 428 */
 429function wpautop( $pee, $br = true ) {
 430	$pre_tags = array();
 431
 432	if ( trim($pee) === '' )
 433		return '';
 434
 435	// Just to make things a little easier, pad the end.
 436	$pee = $pee . "\n";
 437
 438	/*
 439	 * Pre tags shouldn't be touched by autop.
 440	 * Replace pre tags with placeholders and bring them back after autop.
 441	 */
 442	if ( strpos($pee, '<pre') !== false ) {
 443		$pee_parts = explode( '</pre>', $pee );
 444		$last_pee = array_pop($pee_parts);
 445		$pee = '';
 446		$i = 0;
 447
 448		foreach ( $pee_parts as $pee_part ) {
 449			$start = strpos($pee_part, '<pre');
 450
 451			// Malformed html?
 452			if ( $start === false ) {
 453				$pee .= $pee_part;
 454				continue;
 455			}
 456
 457			$name = "<pre wp-pre-tag-$i></pre>";
 458			$pre_tags[$name] = substr( $pee_part, $start ) . '</pre>';
 459
 460			$pee .= substr( $pee_part, 0, $start ) . $name;
 461			$i++;
 462		}
 463
 464		$pee .= $last_pee;
 465	}
 466	// Change multiple <br>s into two line breaks, which will turn into paragraphs.
 467	$pee = preg_replace('|<br\s*/?>\s*<br\s*/?>|', "\n\n", $pee);
 468
 469	$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)';
 470
 471	// Add a single line break above block-level opening tags.
 472	$pee = preg_replace('!(<' . $allblocks . '[\s/>])!', "\n$1", $pee);
 473
 474	// Add a double line break below block-level closing tags.
 475	$pee = preg_replace('!(</' . $allblocks . '>)!', "$1\n\n", $pee);
 476
 477	// Standardize newline characters to "\n".
 478	$pee = str_replace(array("\r\n", "\r"), "\n", $pee);
 479
 480	// Find newlines in all elements and add placeholders.
 481	$pee = wp_replace_in_html_tags( $pee, array( "\n" => " <!-- wpnl --> " ) );
 482
 483	// Collapse line breaks before and after <option> elements so they don't get autop'd.
 484	if ( strpos( $pee, '<option' ) !== false ) {
 485		$pee = preg_replace( '|\s*<option|', '<option', $pee );
 486		$pee = preg_replace( '|</option>\s*|', '</option>', $pee );
 487	}
 488
 489	/*
 490	 * Collapse line breaks inside <object> elements, before <param> and <embed> elements
 491	 * so they don't get autop'd.
 492	 */
 493	if ( strpos( $pee, '</object>' ) !== false ) {
 494		$pee = preg_replace( '|(<object[^>]*>)\s*|', '$1', $pee );
 495		$pee = preg_replace( '|\s*</object>|', '</object>', $pee );
 496		$pee = preg_replace( '%\s*(</?(?:param|embed)[^>]*>)\s*%', '$1', $pee );
 497	}
 498
 499	/*
 500	 * Collapse line breaks inside <audio> and <video> elements,
 501	 * before and after <source> and <track> elements.
 502	 */
 503	if ( strpos( $pee, '<source' ) !== false || strpos( $pee, '<track' ) !== false ) {
 504		$pee = preg_replace( '%([<\[](?:audio|video)[^>\]]*[>\]])\s*%', '$1', $pee );
 505		$pee = preg_replace( '%\s*([<\[]/(?:audio|video)[>\]])%', '$1', $pee );
 506		$pee = preg_replace( '%\s*(<(?:source|track)[^>]*>)\s*%', '$1', $pee );
 507	}
 508
 509	// Remove more than two contiguous line breaks.
 510	$pee = preg_replace("/\n\n+/", "\n\n", $pee);
 511
 512	// Split up the contents into an array of strings, separated by double line breaks.
 513	$pees = preg_split('/\n\s*\n/', $pee, -1, PREG_SPLIT_NO_EMPTY);
 514
 515	// Reset $pee prior to rebuilding.
 516	$pee = '';
 517
 518	// Rebuild the content as a string, wrapping every bit with a <p>.
 519	foreach ( $pees as $tinkle ) {
 520		$pee .= '<p>' . trim($tinkle, "\n") . "</p>\n";
 521	}
 522
 523	// Under certain strange conditions it could create a P of entirely whitespace.
 524	$pee = preg_replace('|<p>\s*</p>|', '', $pee);
 525
 526	// Add a closing <p> inside <div>, <address>, or <form> tag if missing.
 527	$pee = preg_replace('!<p>([^<]+)</(div|address|form)>!', "<p>$1</p></$2>", $pee);
 528
 529	// If an opening or closing block element tag is wrapped in a <p>, unwrap it.
 530	$pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee);
 531
 532	// In some cases <li> may get wrapped in <p>, fix them.
 533	$pee = preg_replace("|<p>(<li.+?)</p>|", "$1", $pee);
 534
 535	// If a <blockquote> is wrapped with a <p>, move it inside the <blockquote>.
 536	$pee = preg_replace('|<p><blockquote([^>]*)>|i', "<blockquote$1><p>", $pee);
 537	$pee = str_replace('</blockquote></p>', '</p></blockquote>', $pee);
 538
 539	// If an opening or closing block element tag is preceded by an opening <p> tag, remove it.
 540	$pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)!', "$1", $pee);
 541
 542	// If an opening or closing block element tag is followed by a closing <p> tag, remove it.
 543	$pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee);
 544
 545	// Optionally insert line breaks.
 546	if ( $br ) {
 547		// Replace newlines that shouldn't be touched with a placeholder.
 548		$pee = preg_replace_callback('/<(script|style).*?<\/\\1>/s', '_autop_newline_preservation_helper', $pee);
 549
 550		// Normalize <br>
 551		$pee = str_replace( array( '<br>', '<br/>' ), '<br />', $pee );
 552
 553		// Replace any new line characters that aren't preceded by a <br /> with a <br />.
 554		$pee = preg_replace('|(?<!<br />)\s*\n|', "<br />\n", $pee);
 555
 556		// Replace newline placeholders with newlines.
 557		$pee = str_replace('<WPPreserveNewline />', "\n", $pee);
 558	}
 559
 560	// If a <br /> tag is after an opening or closing block tag, remove it.
 561	$pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*<br />!', "$1", $pee);
 562
 563	// If a <br /> tag is before a subset of opening or closing block tags, remove it.
 564	$pee = preg_replace('!<br />(\s*</?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)!', '$1', $pee);
 565	$pee = preg_replace( "|\n</p>$|", '</p>', $pee );
 566
 567	// Replace placeholder <pre> tags with their original content.
 568	if ( !empty($pre_tags) )
 569		$pee = str_replace(array_keys($pre_tags), array_values($pre_tags), $pee);
 570
 571	// Restore newlines in all elements.
 572	if ( false !== strpos( $pee, '<!-- wpnl -->' ) ) {
 573		$pee = str_replace( array( ' <!-- wpnl --> ', '<!-- wpnl -->' ), "\n", $pee );
 574	}
 575
 576	return $pee;
 577}
 578
 579/**
 580 * Separate HTML elements and comments from the text.
 581 *
 582 * @since 4.2.4
 583 *
 584 * @param string $input The text which has to be formatted.
 585 * @return array The formatted text.
 586 */
 587function wp_html_split( $input ) {
 588	return preg_split( get_html_split_regex(), $input, -1, PREG_SPLIT_DELIM_CAPTURE );
 589}
 590
 591/**
 592 * Retrieve the regular expression for an HTML element.
 593 *
 594 * @since 4.4.0
 595 *
 596 * @return string The regular expression
 597 */
 598function get_html_split_regex() {
 599	static $regex;
 600
 601	if ( ! isset( $regex ) ) {
 602		$comments =
 603			  '!'           // Start of comment, after the <.
 604			. '(?:'         // Unroll the loop: Consume everything until --> is found.
 605			.     '-(?!->)' // Dash not followed by end of comment.
 606			.     '[^\-]*+' // Consume non-dashes.
 607			. ')*+'         // Loop possessively.
 608			. '(?:-->)?';   // End of comment. If not found, match all input.
 609
 610		$cdata =
 611			  '!\[CDATA\['  // Start of comment, after the <.
 612			. '[^\]]*+'     // Consume non-].
 613			. '(?:'         // Unroll the loop: Consume everything until ]]> is found.
 614			.     '](?!]>)' // One ] not followed by end of comment.
 615			.     '[^\]]*+' // Consume non-].
 616			. ')*+'         // Loop possessively.
 617			. '(?:]]>)?';   // End of comment. If not found, match all input.
 618
 619		$escaped =
 620			  '(?='           // Is the element escaped?
 621			.    '!--'
 622			. '|'
 623			.    '!\[CDATA\['
 624			. ')'
 625			. '(?(?=!-)'      // If yes, which type?
 626			.     $comments
 627			. '|'
 628			.     $cdata
 629			. ')';
 630
 631		$regex =
 632			  '/('              // Capture the entire match.
 633			.     '<'           // Find start of element.
 634			.     '(?'          // Conditional expression follows.
 635			.         $escaped  // Find end of escaped element.
 636			.     '|'           // ... else ...
 637			.         '[^>]*>?' // Find end of normal element.
 638			.     ')'
 639			. ')/';
 640	}
 641
 642	return $regex;
 643}
 644
 645/**
 646 * Retrieve the combined regular expression for HTML and shortcodes.
 647 *
 648 * @access private
 649 * @ignore
 650 * @internal This function will be removed in 4.5.0 per Shortcode API Roadmap.
 651 * @since 4.4.0
 652 *
 653 * @param string $shortcode_regex The result from _get_wptexturize_shortcode_regex().  Optional.
 654 * @return string The regular expression
 655 */
 656function _get_wptexturize_split_regex( $shortcode_regex = '' ) {
 657	static $html_regex;
 658
 659	if ( ! isset( $html_regex ) ) {
 660		$comment_regex =
 661			  '!'           // Start of comment, after the <.
 662			. '(?:'         // Unroll the loop: Consume everything until --> is found.
 663			.     '-(?!->)' // Dash not followed by end of comment.
 664			.     '[^\-]*+' // Consume non-dashes.
 665			. ')*+'         // Loop possessively.
 666			. '(?:-->)?';   // End of comment. If not found, match all input.
 667
 668		$html_regex =			 // Needs replaced with wp_html_split() per Shortcode API Roadmap.
 669			  '<'                // Find start of element.
 670			. '(?(?=!--)'        // Is this a comment?
 671			.     $comment_regex // Find end of comment.
 672			. '|'
 673			.     '[^>]*>?'      // Find end of element. If not found, match all input.
 674			. ')';
 675	}
 676
 677	if ( empty( $shortcode_regex ) ) {
 678		$regex = '/(' . $html_regex . ')/';
 679	} else {
 680		$regex = '/(' . $html_regex . '|' . $shortcode_regex . ')/';
 681	}
 682
 683	return $regex;
 684}
 685
 686/**
 687 * Retrieve the regular expression for shortcodes.
 688 *
 689 * @access private
 690 * @ignore
 691 * @internal This function will be removed in 4.5.0 per Shortcode API Roadmap.
 692 * @since 4.4.0
 693 *
 694 * @param array $tagnames List of shortcodes to find.
 695 * @return string The regular expression
 696 */
 697function _get_wptexturize_shortcode_regex( $tagnames ) {
 698	$tagregexp = join( '|', array_map( 'preg_quote', $tagnames ) );
 699	$tagregexp = "(?:$tagregexp)(?=[\\s\\]\\/])"; // Excerpt of get_shortcode_regex().
 700	$regex =
 701		  '\['              // Find start of shortcode.
 702		. '[\/\[]?'         // Shortcodes may begin with [/ or [[
 703		. $tagregexp        // Only match registered shortcodes, because performance.
 704		. '(?:'
 705		.     '[^\[\]<>]+'  // Shortcodes do not contain other shortcodes. Quantifier critical.
 706		. '|'
 707		.     '<[^\[\]>]*>' // HTML elements permitted. Prevents matching ] before >.
 708		. ')*+'             // Possessive critical.
 709		. '\]'              // Find end of shortcode.
 710		. '\]?';            // Shortcodes may end with ]]
 711
 712	return $regex;
 713}
 714
 715/**
 716 * Replace characters or phrases within HTML elements only.
 717 *
 718 * @since 4.2.3
 719 *
 720 * @param string $haystack The text which has to be formatted.
 721 * @param array $replace_pairs In the form array('from' => 'to', ...).
 722 * @return string The formatted text.
 723 */
 724function wp_replace_in_html_tags( $haystack, $replace_pairs ) {
 725	// Find all elements.
 726	$textarr = wp_html_split( $haystack );
 727	$changed = false;
 728
 729	// Optimize when searching for one item.
 730	if ( 1 === count( $replace_pairs ) ) {
 731		// Extract $needle and $replace.
 732		foreach ( $replace_pairs as $needle => $replace );
 733
 734		// Loop through delimiters (elements) only.
 735		for ( $i = 1, $c = count( $textarr ); $i < $c; $i += 2 ) {
 736			if ( false !== strpos( $textarr[$i], $needle ) ) {
 737				$textarr[$i] = str_replace( $needle, $replace, $textarr[$i] );
 738				$changed = true;
 739			}
 740		}
 741	} else {
 742		// Extract all $needles.
 743		$needles = array_keys( $replace_pairs );
 744
 745		// Loop through delimiters (elements) only.
 746		for ( $i = 1, $c = count( $textarr ); $i < $c; $i += 2 ) {
 747			foreach ( $needles as $needle ) {
 748				if ( false !== strpos( $textarr[$i], $needle ) ) {
 749					$textarr[$i] = strtr( $textarr[$i], $replace_pairs );
 750					$changed = true;
 751					// After one strtr() break out of the foreach loop and look at next element.
 752					break;
 753				}
 754			}
 755		}
 756	}
 757
 758	if ( $changed ) {
 759		$haystack = implode( $textarr );
 760	}
 761
 762	return $haystack;
 763}
 764
 765/**
 766 * Newline preservation help function for wpautop
 767 *
 768 * @since 3.1.0
 769 * @access private
 770 *
 771 * @param array $matches preg_replace_callback matches array
 772 * @return string
 773 */
 774function _autop_newline_preservation_helper( $matches ) {
 775	return str_replace( "\n", "<WPPreserveNewline />", $matches[0] );
 776}
 777
 778/**
 779 * Don't auto-p wrap shortcodes that stand alone
 780 *
 781 * Ensures that shortcodes are not wrapped in `<p>...</p>`.
 782 *
 783 * @since 2.9.0
 784 *
 785 * @global array $shortcode_tags
 786 *
 787 * @param string $pee The content.
 788 * @return string The filtered content.
 789 */
 790function shortcode_unautop( $pee ) {
 791	global $shortcode_tags;
 792
 793	if ( empty( $shortcode_tags ) || !is_array( $shortcode_tags ) ) {
 794		return $pee;
 795	}
 796
 797	$tagregexp = join( '|', array_map( 'preg_quote', array_keys( $shortcode_tags ) ) );
 798	$spaces = wp_spaces_regexp();
 799
 800	$pattern =
 801		  '/'
 802		. '<p>'                              // Opening paragraph
 803		. '(?:' . $spaces . ')*+'            // Optional leading whitespace
 804		. '('                                // 1: The shortcode
 805		.     '\\['                          // Opening bracket
 806		.     "($tagregexp)"                 // 2: Shortcode name
 807		.     '(?![\\w-])'                   // Not followed by word character or hyphen
 808		                                     // Unroll the loop: Inside the opening shortcode tag
 809		.     '[^\\]\\/]*'                   // Not a closing bracket or forward slash
 810		.     '(?:'
 811		.         '\\/(?!\\])'               // A forward slash not followed by a closing bracket
 812		.         '[^\\]\\/]*'               // Not a closing bracket or forward slash
 813		.     ')*?'
 814		.     '(?:'
 815		.         '\\/\\]'                   // Self closing tag and closing bracket
 816		.     '|'
 817		.         '\\]'                      // Closing bracket
 818		.         '(?:'                      // Unroll the loop: Optionally, anything between the opening and closing shortcode tags
 819		.             '[^\\[]*+'             // Not an opening bracket
 820		.             '(?:'
 821		.                 '\\[(?!\\/\\2\\])' // An opening bracket not followed by the closing shortcode tag
 822		.                 '[^\\[]*+'         // Not an opening bracket
 823		.             ')*+'
 824		.             '\\[\\/\\2\\]'         // Closing shortcode tag
 825		.         ')?'
 826		.     ')'
 827		. ')'
 828		. '(?:' . $spaces . ')*+'            // optional trailing whitespace
 829		. '<\\/p>'                           // closing paragraph
 830		. '/';
 831
 832	return preg_replace( $pattern, '$1', $pee );
 833}
 834
 835/**
 836 * Checks to see if a string is utf8 encoded.
 837 *
 838 * NOTE: This function checks for 5-Byte sequences, UTF8
 839 *       has Bytes Sequences with a maximum length of 4.
 840 *
 841 * @author bmorel at ssi dot fr (modified)
 842 * @since 1.2.1
 843 *
 844 * @param string $str The string to be checked
 845 * @return bool True if $str fits a UTF-8 model, false otherwise.
 846 */
 847function seems_utf8( $str ) {
 848	mbstring_binary_safe_encoding();
 849	$length = strlen($str);
 850	reset_mbstring_encoding();
 851	for ($i=0; $i < $length; $i++) {
 852		$c = ord($str[$i]);
 853		if ($c < 0x80) $n = 0; // 0bbbbbbb
 854		elseif (($c & 0xE0) == 0xC0) $n=1; // 110bbbbb
 855		elseif (($c & 0xF0) == 0xE0) $n=2; // 1110bbbb
 856		elseif (($c & 0xF8) == 0xF0) $n=3; // 11110bbb
 857		elseif (($c & 0xFC) == 0xF8) $n=4; // 111110bb
 858		elseif (($c & 0xFE) == 0xFC) $n=5; // 1111110b
 859		else return false; // Does not match any model
 860		for ($j=0; $j<$n; $j++) { // n bytes matching 10bbbbbb follow ?
 861			if ((++$i == $length) || ((ord($str[$i]) & 0xC0) != 0x80))
 862				return false;
 863		}
 864	}
 865	return true;
 866}
 867
 868/**
 869 * Converts a number of special characters into their HTML entities.
 870 *
 871 * Specifically deals with: &, <, >, ", and '.
 872 *
 873 * $quote_style can be set to ENT_COMPAT to encode " to
 874 * &quot;, or ENT_QUOTES to do both. Default is ENT_NOQUOTES where no quotes are encoded.
 875 *
 876 * @since 1.2.2
 877 * @access private
 878 *
 879 * @staticvar string $_charset
 880 *
 881 * @param string     $string         The text which is to be encoded.
 882 * @param int|string $quote_style    Optional. Converts double quotes if set to ENT_COMPAT,
 883 *                                   both single and double if set to ENT_QUOTES or none if set to ENT_NOQUOTES.
 884 *                                   Also compatible with old values; converting single quotes if set to 'single',
 885 *                                   double if set to 'double' or both if otherwise set.
 886 *                                   Default is ENT_NOQUOTES.
 887 * @param string     $charset        Optional. The character encoding of the string. Default is false.
 888 * @param bool       $double_encode  Optional. Whether to encode existing html entities. Default is false.
 889 * @return string The encoded text with HTML entities.
 890 */
 891function _wp_specialchars( $string, $quote_style = ENT_NOQUOTES, $charset = false, $double_encode = false ) {
 892	$string = (string) $string;
 893
 894	if ( 0 === strlen( $string ) )
 895		return '';
 896
 897	// Don't bother if there are no specialchars - saves some processing
 898	if ( ! preg_match( '/[&<>"\']/', $string ) )
 899		return $string;
 900
 901	// Account for the previous behaviour of the function when the $quote_style is not an accepted value
 902	if ( empty( $quote_style ) )
 903		$quote_style = ENT_NOQUOTES;
 904	elseif ( ! in_array( $quote_style, array( 0, 2, 3, 'single', 'double' ), true ) )
 905		$quote_style = ENT_QUOTES;
 906
 907	// Store the site charset as a static to avoid multiple calls to wp_load_alloptions()
 908	if ( ! $charset ) {
 909		static $_charset = null;
 910		if ( ! isset( $_charset ) ) {
 911			$alloptions = wp_load_alloptions();
 912			$_charset = isset( $alloptions['blog_charset'] ) ? $alloptions['blog_charset'] : '';
 913		}
 914		$charset = $_charset;
 915	}
 916
 917	if ( in_array( $charset, array( 'utf8', 'utf-8', 'UTF8' ) ) )
 918		$charset = 'UTF-8';
 919
 920	$_quote_style = $quote_style;
 921
 922	if ( $quote_style === 'double' ) {
 923		$quote_style = ENT_COMPAT;
 924		$_quote_style = ENT_COMPAT;
 925	} elseif ( $quote_style === 'single' ) {
 926		$quote_style = ENT_NOQUOTES;
 927	}
 928
 929	if ( ! $double_encode ) {
 930		// Guarantee every &entity; is valid, convert &garbage; into &amp;garbage;
 931		// This is required for PHP < 5.4.0 because ENT_HTML401 flag is unavailable.
 932		$string = wp_kses_normalize_entities( $string );
 933	}
 934
 935	$string = @htmlspecialchars( $string, $quote_style, $charset, $double_encode );
 936
 937	// Backwards compatibility
 938	if ( 'single' === $_quote_style )
 939		$string = str_replace( "'", '&#039;', $string );
 940
 941	return $string;
 942}
 943
 944/**
 945 * Converts a number of HTML entities into their special characters.
 946 *
 947 * Specifically deals with: &, <, >, ", and '.
 948 *
 949 * $quote_style can be set to ENT_COMPAT to decode " entities,
 950 * or ENT_QUOTES to do both " and '. Default is ENT_NOQUOTES where no quotes are decoded.
 951 *
 952 * @since 2.8.0
 953 *
 954 * @param string     $string The text which is to be decoded.
 955 * @param string|int $quote_style Optional. Converts double quotes if set to ENT_COMPAT,
 956 *                                both single and double if set to ENT_QUOTES or
 957 *                                none if set to ENT_NOQUOTES.
 958 *                                Also compatible with old _wp_specialchars() values;
 959 *                                converting single quotes if set to 'single',
 960 *                                double if set to 'double' or both if otherwise set.
 961 *                                Default is ENT_NOQUOTES.
 962 * @return string The decoded text without HTML entities.
 963 */
 964function wp_specialchars_decode( $string, $quote_style = ENT_NOQUOTES ) {
 965	$string = (string) $string;
 966
 967	if ( 0 === strlen( $string ) ) {
 968		return '';
 969	}
 970
 971	// Don't bother if there are no entities - saves a lot of processing
 972	if ( strpos( $string, '&' ) === false ) {
 973		return $string;
 974	}
 975
 976	// Match the previous behaviour of _wp_specialchars() when the $quote_style is not an accepted value
 977	if ( empty( $quote_style ) ) {
 978		$quote_style = ENT_NOQUOTES;
 979	} elseif ( !in_array( $quote_style, array( 0, 2, 3, 'single', 'double' ), true ) ) {
 980		$quote_style = ENT_QUOTES;
 981	}
 982
 983	// More complete than get_html_translation_table( HTML_SPECIALCHARS )
 984	$single = array( '&#039;'  => '\'', '&#x27;' => '\'' );
 985	$single_preg = array( '/&#0*39;/'  => '&#039;', '/&#x0*27;/i' => '&#x27;' );
 986	$double = array( '&quot;' => '"', '&#034;'  => '"', '&#x22;' => '"' );
 987	$double_preg = array( '/&#0*34;/'  => '&#034;', '/&#x0*22;/i' => '&#x22;' );
 988	$others = array( '&lt;'   => '<', '&#060;'  => '<', '&gt;'   => '>', '&#062;'  => '>', '&amp;'  => '&', '&#038;'  => '&', '&#x26;' => '&' );
 989	$others_preg = array( '/&#0*60;/'  => '&#060;', '/&#0*62;/'  => '&#062;', '/&#0*38;/'  => '&#038;', '/&#x0*26;/i' => '&#x26;' );
 990
 991	if ( $quote_style === ENT_QUOTES ) {
 992		$translation = array_merge( $single, $double, $others );
 993		$translation_preg = array_merge( $single_preg, $double_preg, $others_preg );
 994	} elseif ( $quote_style === ENT_COMPAT || $quote_style === 'double' ) {
 995		$translation = array_merge( $double, $others );
 996		$translation_preg = array_merge( $double_preg, $others_preg );
 997	} elseif ( $quote_style === 'single' ) {
 998		$translation = array_merge( $single, $others );
 999		$translation_preg = array_merge( $single_preg, $others_preg );
1000	} elseif ( $quote_style === ENT_NOQUOTES ) {
1001		$translation = $others;
1002		$translation_preg = $others_preg;
1003	}
1004
1005	// Remove zero padding on numeric entities
1006	$string = preg_replace( array_keys( $translation_preg ), array_values( $translation_preg ), $string );
1007
1008	// Replace characters according to translation table
1009	return strtr( $string, $translation );
1010}
1011
1012/**
1013 * Checks for invalid UTF8 in a string.
1014 *
1015 * @since 2.8.0
1016 *
1017 * @staticvar bool $is_utf8
1018 * @staticvar bool $utf8_pcre
1019 *
1020 * @param string  $string The text which is to be checked.
1021 * @param bool    $strip Optional. Whether to attempt to strip out invalid UTF8. Default is false.
1022 * @return string The checked text.
1023 */
1024function wp_check_invalid_utf8( $string, $strip = false ) {
1025	$string = (string) $string;
1026
1027	if ( 0 === strlen( $string ) ) {
1028		return '';
1029	}
1030
1031	// Store the site charset as a static to avoid multiple calls to get_option()
1032	static $is_utf8 = null;
1033	if ( ! isset( $is_utf8 ) ) {
1034		$is_utf8 = in_array( get_option( 'blog_charset' ), array( 'utf8', 'utf-8', 'UTF8', 'UTF-8' ) );
1035	}
1036	if ( ! $is_utf8 ) {
1037		return $string;
1038	}
1039
1040	// Check for support for utf8 in the installed PCRE library once and store the result in a static
1041	static $utf8_pcre = null;
1042	if ( ! isset( $utf8_pcre ) ) {
1043		$utf8_pcre = @preg_match( '/^./u', 'a' );
1044	}
1045	// We can't demand utf8 in the PCRE installation, so just return the string in those cases
1046	if ( !$utf8_pcre ) {
1047		return $string;
1048	}
1049
1050	// preg_match fails when it encounters invalid UTF8 in $string
1051	if ( 1 === @preg_match( '/^./us', $string ) ) {
1052		return $string;
1053	}
1054
1055	// Attempt to strip the bad chars if requested (not recommended)
1056	if ( $strip && function_exists( 'iconv' ) ) {
1057		return iconv( 'utf-8', 'utf-8', $string );
1058	}
1059
1060	return '';
1061}
1062
1063/**
1064 * Encode the Unicode values to be used in the URI.
1065 *
1066 * @since 1.5.0
1067 *
1068 * @param string $utf8_string
1069 * @param int    $length Max  length of the string
1070 * @return string String with Unicode encoded for URI.
1071 */
1072function utf8_uri_encode( $utf8_string, $length = 0 ) {
1073	$unicode = '';
1074	$values = array();
1075	$num_octets = 1;
1076	$unicode_length = 0;
1077
1078	mbstring_binary_safe_encoding();
1079	$string_length = strlen( $utf8_string );
1080	reset_mbstring_encoding();
1081
1082	for ($i = 0; $i < $string_length; $i++ ) {
1083
1084		$value = ord( $utf8_string[ $i ] );
1085
1086		if ( $value < 128 ) {
1087			if ( $length && ( $unicode_length >= $length ) )
1088				break;
1089			$unicode .= chr($value);
1090			$unicode_length++;
1091		} else {
1092			if ( count( $values ) == 0 ) {
1093				if ( $value < 224 ) {
1094					$num_octets = 2;
1095				} elseif ( $value < 240 ) {
1096					$num_octets = 3;
1097				} else {
1098					$num_octets = 4;
1099				}
1100			}
1101
1102			$values[] = $value;
1103
1104			if ( $length && ( $unicode_length + ($num_octets * 3) ) > $length )
1105				break;
1106			if ( count( $values ) == $num_octets ) {
1107				for ( $j = 0; $j < $num_octets; $j++ ) {
1108					$unicode .= '%' . dechex( $values[ $j ] );
1109				}
1110
1111				$unicode_length += $num_octets * 3;
1112
1113				$values = array();
1114				$num_octets = 1;
1115			}
1116		}
1117	}
1118
1119	return $unicode;
1120}
1121
1122/**
1123 * Converts all accent characters to ASCII characters.
1124 *
1125 * If there are no accent characters, then the string given is just returned.
1126 *
1127 * @since 1.2.1
1128 *
1129 * @param string $string Text that might have accent characters
1130 * @return string Filtered string with replaced "nice" characters.
1131 */
1132function remove_accents( $string ) {
1133	if ( !preg_match('/[\x80-\xff]/', $string) )
1134		return $string;
1135
1136	if (seems_utf8($string)) {
1137		$chars = array(
1138		// Decompositions for Latin-1 Supplement
1139		chr(194).chr(170) => 'a', chr(194).chr(186) => 'o',
1140		chr(195).chr(128) => 'A', chr(195).chr(129) => 'A',
1141		chr(195).chr(130) => 'A', chr(195).chr(131) => 'A',
1142		chr(195).chr(132) => 'A', chr(195).chr(133) => 'A',
1143		chr(195).chr(134) => 'AE',chr(195).chr(135) => 'C',
1144		chr(195).chr(136) => 'E', chr(195).chr(137) => 'E',
1145		chr(195).chr(138) => 'E', chr(195).chr(139) => 'E',
1146		chr(195).chr(140) => 'I', chr(195).chr(141) => 'I',
1147		chr(195).chr(142) => 'I', chr(195).chr(143) => 'I',
1148		chr(195).chr(144) => 'D', chr(195).chr(145) => 'N',
1149		chr(195).chr(146) => 'O', chr(195).chr(147) => 'O',
1150		chr(195).chr(148) => 'O', chr(195).chr(149) => 'O',
1151		chr(195).chr(150) => 'O', chr(195).chr(153) => 'U',
1152		chr(195).chr(154) => 'U', chr(195).chr(155) => 'U',
1153		chr(195).chr(156) => 'U', chr(195).chr(157) => 'Y',
1154		chr(195).chr(158) => 'TH',chr(195).chr(159) => 's',
1155		chr(195).chr(160) => 'a', chr(195).chr(161) => 'a',
1156		chr(195).chr(162) => 'a', chr(195).chr(163) => 'a',
1157		chr(195).chr(164) => 'a', chr(195).chr(165) => 'a',
1158		chr(195).chr(166) => 'ae',chr(195).chr(167) => 'c',
1159		chr(195).chr(168) => 'e', chr(195).chr(169) => 'e',
1160		chr(195).chr(170) => 'e', chr(195).chr(171) => 'e',
1161		chr(195).chr(172) => 'i', chr(195).chr(173) => 'i',
1162		chr(195).chr(174) => 'i', chr(195).chr(175) => 'i',
1163		chr(195).chr(176) => 'd', chr(195).chr(177) => 'n',
1164		chr(195).chr(178) => 'o', chr(195).chr(179) => 'o',
1165		chr(195).chr(180) => 'o', chr(195).chr(181) => 'o',
1166		chr(195).chr(182) => 'o', chr(195).chr(184) => 'o',
1167		chr(195).chr(185) => 'u', chr(195).chr(186) => 'u',
1168		chr(195).chr(187) => 'u', chr(195).chr(188) => 'u',
1169		chr(195).chr(189) => 'y', chr(195).chr(190) => 'th',
1170		chr(195).chr(191) => 'y', chr(195).chr(152) => 'O',
1171		// Decompositions for Latin Extended-A
1172		chr(196).chr(128) => 'A', chr(196).chr(129) => 'a',
1173		chr(196).chr(130) => 'A', chr(196).chr(131) => 'a',
1174		chr(196).chr(132) => 'A', chr(196).chr(133) => 'a',
1175		chr(196).chr(134) => 'C', chr(196).chr(135) => 'c',
1176		chr(196).chr(136) => 'C', chr(196).chr(137) => 'c',
1177		chr(196).chr(138) => 'C', chr(196).chr(139) => 'c',
1178		chr(196).chr(140) => 'C', chr(196).chr(141) => 'c',
1179		chr(196).chr(142) => 'D', chr(196).chr(143) => 'd',
1180		chr(196).chr(144) => 'D', chr(196).chr(145) => 'd',
1181		chr(196).chr(146) => 'E', chr(196).chr(147) => 'e',
1182		chr(196).chr(148) => 'E', chr(196).chr(149) => 'e',
1183		chr(196).chr(150) => 'E', chr(196).chr(151) => 'e',
1184		chr(196).chr(152) => 'E', chr(196).chr(153) => 'e',
1185		chr(196).chr(154) => 'E', chr(196).chr(155) => 'e',
1186		chr(196).chr(156) => 'G', chr(196).chr(157) => 'g',
1187		chr(196).chr(158) => 'G', chr(196).chr(159) => 'g',
1188		chr(196).chr(160) => 'G', chr(196).chr(161) => 'g',
1189		chr(196).chr(162) => 'G', chr(196).chr(163) => 'g',
1190		chr(196).chr(164) => 'H', chr(196).chr(165) => 'h',
1191		chr(196).chr(166) => 'H', chr(196).chr(167) => 'h',
1192		chr(196).chr(168) => 'I', chr(196).chr(169) => 'i',
1193		chr(196).chr(170) => 'I', chr(196).chr(171) => 'i',
1194		chr(196).chr(172) => 'I', chr(196).chr(173) => 'i',
1195		chr(196).chr(174) => 'I', chr(196).chr(175) => 'i',
1196		chr(196).chr(176) => 'I', chr(196).chr(177) => 'i',
1197		chr(196).chr(178) => 'IJ',chr(196).chr(179) => 'ij',
1198		chr(196).chr(180) => 'J', chr(196).chr(181) => 'j',
1199		chr(196).chr(182) => 'K', chr(196).chr(183) => 'k',
1200		chr(196).chr(184) => 'k', chr(196).chr(185) => 'L',
1201		chr(196).chr(186) => 'l', chr(196).chr(187) => 'L',
1202		chr(196).chr(188) => 'l', chr(196).chr(189) => 'L',
1203		chr(196).chr(190) => 'l', chr(196).chr(191) => 'L',
1204		chr(197).chr(128) => 'l', chr(197).chr(129) => 'L',
1205		chr(197).chr(130) => 'l', chr(197).chr(131) => 'N',
1206		chr(197).chr(132) => 'n', chr(197).chr(133) => 'N',
1207		chr(197).chr(134) => 'n', chr(197).chr(135) => 'N',
1208		chr(197).chr(136) => 'n', chr(197).chr(137) => 'N',
1209		chr(197).chr(138) => 'n', chr(197).chr(139) => 'N',
1210		chr(197).chr(140) => 'O', chr(197).chr(141) => 'o',
1211		chr(197).chr(142) => 'O', chr(197).chr(143) => 'o',
1212		chr(197).chr(144) => 'O', chr(197).chr(145) => 'o',
1213		chr(197).chr(146) => 'OE',chr(197).chr(147) => 'oe',
1214		chr(197).chr(148) => 'R',chr(197).chr(149) => 'r',
1215		chr(197).chr(150) => 'R',chr(197).chr(151) => 'r',
1216		chr(197).chr(152) => 'R',chr(197).chr(153) => 'r',
1217		chr(197).chr(154) => 'S',chr(197).chr(155) => 's',
1218		chr(197).chr(156) => 'S',chr(197).chr(157) => 's',
1219		chr(197).chr(158) => 'S',chr(197).chr(159) => 's',
1220		chr(197).chr(160) => 'S', chr(197).chr(161) => 's',
1221		chr(197).chr(162) => 'T', chr(197).chr(163) => 't',
1222		chr(197).chr(164) => 'T', chr(197).chr(165) => 't',
1223		chr(197).chr(166) => 'T', chr(197).chr(167) => 't',
1224		chr(197).chr(168) => 'U', chr(197).chr(169) => 'u',
1225		chr(197).chr(170) => 'U', chr(197).chr(171) => 'u',
1226		chr(197).chr(172) => 'U', chr(197).chr(173) => 'u',
1227		chr(197).chr(174) => 'U', chr(197).chr(175) => 'u',
1228		chr(197).chr(176) => 'U', chr(197).chr(177) => 'u',
1229		chr(197).chr(178) => 'U', chr(197).chr(179) => 'u',
1230		chr(197).chr(180) => 'W', chr(197).chr(181) => 'w',
1231		chr(197).chr(182) => 'Y', chr(197).chr(183) => 'y',
1232		chr(197).chr(184) => 'Y', chr(197).chr(185) => 'Z',
1233		chr(197).chr(186) => 'z', chr(197).chr(187) => 'Z',
1234		chr(197).chr(188) => 'z', chr(197).chr(189) => 'Z',
1235		chr(197).chr(190) => 'z', chr(197).chr(191) => 's',
1236		// Decompositions for Latin Extended-B
1237		chr(200).chr(152) => 'S', chr(200).chr(153) => 's',
1238		chr(200).chr(154) => 'T', chr(200).chr(155) => 't',
1239		// Euro Sign
1240		chr(226).chr(130).chr(172) => 'E',
1241		// GBP (Pound) Sign
1242		chr(194).chr(163) => '',
1243		// Vowels with diacritic (Vietnamese)
1244		// unmarked
1245		chr(198).chr(160) => 'O', chr(198).chr(161) => 'o',
1246		chr(198).chr(175) => 'U', chr(198).chr(176) => 'u',
1247		// grave accent
1248		chr(225).chr(186).chr(166) => 'A', chr(225).chr(186).chr(167) => 'a',
1249		chr(225).chr(186).chr(176) => 'A', chr(225).chr(186).chr(177) => 'a',
1250		chr(225).chr(187).chr(128) => 'E', chr(225).chr(187).chr(129) => 'e',
1251		chr(225).chr(187).chr(146) => 'O', chr(225).chr(187).chr(147) => 'o',
1252		chr(225).chr(187).chr(156) => 'O', chr(225).chr(187).chr(157) => 'o',
1253		chr(225).chr(187).chr(170) => 'U', chr(225).chr(187).chr(171) => 'u',
1254		chr(225).chr(187).chr(178) => 'Y', chr(225).chr(187).chr(179) => 'y',
1255		// hook
1256		chr(225).chr(186).chr(162) => 'A', chr(225).chr(186).chr(163) => 'a',
1257		chr(225).chr(186).chr(168) => 'A', chr(225).chr(186).chr(169) => 'a',
1258		chr(225).chr(186).chr(178) => 'A', chr(225).chr(186).chr(179) => 'a',
1259		chr(225).chr(186).chr(186) => 'E', chr(225).chr(186).chr(187) => 'e',
1260		chr(225).chr(187).chr(130) => 'E', chr(225).chr(187).chr(131) => 'e',
1261		chr(225).chr(187).chr(136) => 'I', chr(225).chr(187).chr(137) => 'i',
1262		chr(225).chr(187).chr(142) => 'O', chr(225).chr(187).chr(143) => 'o',
1263		chr(225).chr(187).chr(148) => 'O', chr(225).chr(187).chr(149) => 'o',
1264		chr(225).chr(187).chr(158) => 'O', chr(225).chr(187).chr(159) => 'o',
1265		chr(225).chr(187).chr(166) => 'U', chr(225).chr(187).chr(167) => 'u',
1266		chr(225).chr(187).chr(172) => 'U', chr(225).chr(187).chr(173) => 'u',
1267		chr(225).chr(187).chr(182) => 'Y', chr(225).chr(187).chr(183) => 'y',
1268		// tilde
1269		chr(225).chr(186).chr(170) => 'A', chr(225).chr(186).chr(171) => 'a',
1270		chr(225).chr(186).chr(180) => 'A', chr(225).chr(186).chr(181) => 'a',
1271		chr(225).chr(186).chr(188) => 'E', chr(225).chr(186).chr(189) => 'e',
1272		chr(225).chr(187).chr(132) => 'E', chr(225).chr(187).chr(133) => 'e',
1273		chr(225).chr(187).chr(150) => 'O', chr(225).chr(187).chr(151) => 'o',
1274		chr(225).chr(187).chr(160) => 'O', chr(225).chr(187).chr(161) => 'o',
1275		chr(225).chr(187).chr(174) => 'U', chr(225).chr(187).chr(175) => 'u',
1276		chr(225).chr(187).chr(184) => 'Y', chr(225).chr(187).chr(185) => 'y',
1277		// acute accent
1278		chr(225).chr(186).chr(164) => 'A', chr(225).chr(186).chr(165) => 'a',
1279		chr(225).chr(186).chr(174) => 'A', chr(225).chr(186).chr(175) => 'a',
1280		chr(225).chr(186).chr(190) => 'E', chr(225).chr(186).chr(191) => 'e',
1281		chr(225).chr(187).chr(144) => 'O', chr(225).chr(187).chr(145) => 'o',
1282		chr(225).chr(187).chr(154) => 'O', chr(225).chr(187).chr(155) => 'o',
1283		chr(225).chr(187).chr(168) => 'U', chr(225).chr(187).chr(169) => 'u',
1284		// dot below
1285		chr(225).chr(186).chr(160) => 'A', chr(225).chr(186).chr(161) => 'a',
1286		chr(225).chr(186).chr(172) => 'A', chr(225).chr(186).chr(173) => 'a',
1287		chr(225).chr(186).chr(182) => 'A', chr(225).chr(186).chr(183) => 'a',
1288		chr(225).chr(186).chr(184) => 'E', chr(225).chr(186).chr(185) => 'e',
1289		chr(225).chr(187).chr(134) => 'E', chr(225).chr(187).chr(135) => 'e',
1290		chr(225).chr(187).chr(138) => 'I', chr(225).chr(187).chr(139) => 'i',
1291		chr(225).chr(187).chr(140) => 'O', chr(225).chr(187).chr(141) => 'o',
1292		chr(225).chr(187).chr(152) => 'O', chr(225).chr(187).chr(153) => 'o',
1293		chr(225).chr(187).chr(162) => 'O', chr(225).chr(187).chr(163) => 'o',
1294		chr(225).chr(187).chr(164) => 'U', chr(225).chr(187).chr(165) => 'u',
1295		chr(225).chr(187).chr(176) => 'U', chr(225).chr(187).chr(177) => 'u',
1296		chr(225).chr(187).chr(180) => 'Y', chr(225).chr(187).chr(181) => 'y',
1297		// Vowels with diacritic (Chinese, Hanyu Pinyin)
1298		chr(201).chr(145) => 'a',
1299		// macron
1300		chr(199).chr(149) => 'U', chr(199).chr(150) => 'u',
1301		// acute accent
1302		chr(199).chr(151) => 'U', chr(199).chr(152) => 'u',
1303		// caron
1304		chr(199).chr(141) => 'A', chr(199).chr(142) => 'a',
1305		chr(199).chr(143) => 'I', chr(199).chr(144) => 'i',
1306		chr(199).chr(145) => 'O', chr(199).chr(146) => 'o',
1307		chr(199).chr(147) => 'U', chr(199).chr(148) => 'u',
1308		chr(199).chr(153) => 'U', chr(199).chr(154) => 'u',
1309		// grave accent
1310		chr(199).chr(155) => 'U', chr(199).chr(156) => 'u',
1311		);
1312
1313		// Used for locale-specific rules
1314		$locale = get_locale();
1315
1316		if ( 'de_DE' == $localeā€¦

Large files files are truncated, but you can click here to view the full file