PageRenderTime 74ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 2ms

/wp-includes/formatting.php

https://github.com/markjaquith/WordPress
PHP | 6092 lines | 3186 code | 542 blank | 2364 comment | 447 complexity | 07c999e235e53a7a49e2685497a52c26 MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0, LGPL-2.1

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

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