PageRenderTime 85ms CodeModel.GetById 32ms RepoModel.GetById 3ms app.codeStats 1ms

/wp-includes/formatting.php

https://gitlab.com/geyson/geyson
PHP | 4609 lines | 2709 code | 402 blank | 1498 comment | 403 complexity | 491b3be7278d4d351c58be9615a5432f MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0

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

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