· 8 years ago · Dec 25, 2017, 09:16 AM
1<?php
2/**
3 * Main WordPress Formatting API.
4 *
5 * Handles many functions for formatting output.
6 *
7 * @package WordPress
8 */
9
10/**
11 * Replaces common plain text characters into formatted entities
12 *
13 * As an example,
14 *
15 * 'cause today's effort makes it worth tomorrow's "holiday" ...
16 *
17 * Becomes:
18 *
19 * ’cause today’s effort makes it worth tomorrow’s “holiday” …
20 *
21 * Code within certain html blocks are skipped.
22 *
23 * Do not use this function before the {@see 'init'} action hook; everything will break.
24 *
25 * @since 0.71
26 *
27 * @global array $wp_cockneyreplace Array of formatted entities for certain common phrases
28 * @global array $shortcode_tags
29 * @staticvar array $static_characters
30 * @staticvar array $static_replacements
31 * @staticvar array $dynamic_characters
32 * @staticvar array $dynamic_replacements
33 * @staticvar array $default_no_texturize_tags
34 * @staticvar array $default_no_texturize_shortcodes
35 * @staticvar bool $run_texturize
36 * @staticvar string $apos
37 * @staticvar string $prime
38 * @staticvar string $double_prime
39 * @staticvar string $opening_quote
40 * @staticvar string $closing_quote
41 * @staticvar string $opening_single_quote
42 * @staticvar string $closing_single_quote
43 * @staticvar string $open_q_flag
44 * @staticvar string $open_sq_flag
45 * @staticvar string $apos_flag
46 *
47 * @param string $text The text to be formatted
48 * @param bool $reset Set to true for unit testing. Translated patterns will reset.
49 * @return string The string replaced with html entities
50 */
51function wptexturize( $text, $reset = false ) {
52 global $wp_cockneyreplace, $shortcode_tags;
53 static $static_characters = null,
54 $static_replacements = null,
55 $dynamic_characters = null,
56 $dynamic_replacements = null,
57 $default_no_texturize_tags = null,
58 $default_no_texturize_shortcodes = null,
59 $run_texturize = true,
60 $apos = null,
61 $prime = null,
62 $double_prime = null,
63 $opening_quote = null,
64 $closing_quote = null,
65 $opening_single_quote = null,
66 $closing_single_quote = null,
67 $open_q_flag = '<!--oq-->',
68 $open_sq_flag = '<!--osq-->',
69 $apos_flag = '<!--apos-->';
70
71 // If there's nothing to do, just stop.
72 if ( empty( $text ) || false === $run_texturize ) {
73 return $text;
74 }
75
76 // Set up static variables. Run once only.
77 if ( $reset || ! isset( $static_characters ) ) {
78 /**
79 * Filters whether to skip running wptexturize().
80 *
81 * Passing false to the filter will effectively short-circuit wptexturize().
82 * returning the original text passed to the function instead.
83 *
84 * The filter runs only once, the first time wptexturize() is called.
85 *
86 * @since 4.0.0
87 *
88 * @see wptexturize()
89 *
90 * @param bool $run_texturize Whether to short-circuit wptexturize().
91 */
92 $run_texturize = apply_filters( 'run_wptexturize', $run_texturize );
93 if ( false === $run_texturize ) {
94 return $text;
95 }
96
97 /* translators: opening curly double quote */
98 $opening_quote = _x( '“', 'opening curly double quote' );
99 /* translators: closing curly double quote */
100 $closing_quote = _x( '”', 'closing curly double quote' );
101
102 /* translators: apostrophe, for example in 'cause or can't */
103 $apos = _x( '’', 'apostrophe' );
104
105 /* translators: prime, for example in 9' (nine feet) */
106 $prime = _x( '′', 'prime' );
107 /* translators: double prime, for example in 9" (nine inches) */
108 $double_prime = _x( '″', 'double prime' );
109
110 /* translators: opening curly single quote */
111 $opening_single_quote = _x( '‘', 'opening curly single quote' );
112 /* translators: closing curly single quote */
113 $closing_single_quote = _x( '’', 'closing curly single quote' );
114
115 /* translators: en dash */
116 $en_dash = _x( '–', 'en dash' );
117 /* translators: em dash */
118 $em_dash = _x( '—', 'em dash' );
119
120 $default_no_texturize_tags = array('pre', 'code', 'kbd', 'style', 'script', 'tt');
121 $default_no_texturize_shortcodes = array('code');
122
123 // if a plugin has provided an autocorrect array, use it
124 if ( isset($wp_cockneyreplace) ) {
125 $cockney = array_keys( $wp_cockneyreplace );
126 $cockneyreplace = array_values( $wp_cockneyreplace );
127 } else {
128 /* translators: This is a comma-separated list of words that defy the syntax of quotations in normal use,
129 * for example... 'We do not have enough words yet' ... is a typical quoted phrase. But when we write
130 * lines of code 'til we have enough of 'em, then we need to insert apostrophes instead of quotes.
131 */
132 $cockney = explode( ',', _x( "'tain't,'twere,'twas,'tis,'twill,'til,'bout,'nuff,'round,'cause,'em",
133 'Comma-separated list of words to texturize in your language' ) );
134
135 $cockneyreplace = explode( ',', _x( '’tain’t,’twere,’twas,’tis,’twill,’til,’bout,’nuff,’round,’cause,’em',
136 'Comma-separated list of replacement words in your language' ) );
137 }
138
139 $static_characters = array_merge( array( '...', '``', '\'\'', ' (tm)' ), $cockney );
140 $static_replacements = array_merge( array( '…', $opening_quote, $closing_quote, ' ™' ), $cockneyreplace );
141
142
143 // Pattern-based replacements of characters.
144 // Sort the remaining patterns into several arrays for performance tuning.
145 $dynamic_characters = array( 'apos' => array(), 'quote' => array(), 'dash' => array() );
146 $dynamic_replacements = array( 'apos' => array(), 'quote' => array(), 'dash' => array() );
147 $dynamic = array();
148 $spaces = wp_spaces_regexp();
149
150 // '99' and '99" are ambiguous among other patterns; assume it's an abbreviated year at the end of a quotation.
151 if ( "'" !== $apos || "'" !== $closing_single_quote ) {
152 $dynamic[ '/\'(\d\d)\'(?=\Z|[.,:;!?)}\-\]]|>|' . $spaces . ')/' ] = $apos_flag . '$1' . $closing_single_quote;
153 }
154 if ( "'" !== $apos || '"' !== $closing_quote ) {
155 $dynamic[ '/\'(\d\d)"(?=\Z|[.,:;!?)}\-\]]|>|' . $spaces . ')/' ] = $apos_flag . '$1' . $closing_quote;
156 }
157
158 // '99 '99s '99's (apostrophe) But never '9 or '99% or '999 or '99.0.
159 if ( "'" !== $apos ) {
160 $dynamic[ '/\'(?=\d\d(?:\Z|(?![%\d]|[.,]\d)))/' ] = $apos_flag;
161 }
162
163 // Quoted Numbers like '0.42'
164 if ( "'" !== $opening_single_quote && "'" !== $closing_single_quote ) {
165 $dynamic[ '/(?<=\A|' . $spaces . ')\'(\d[.,\d]*)\'/' ] = $open_sq_flag . '$1' . $closing_single_quote;
166 }
167
168 // Single quote at start, or preceded by (, {, <, [, ", -, or spaces.
169 if ( "'" !== $opening_single_quote ) {
170 $dynamic[ '/(?<=\A|[([{"\-]|<|' . $spaces . ')\'/' ] = $open_sq_flag;
171 }
172
173 // Apostrophe in a word. No spaces, double apostrophes, or other punctuation.
174 if ( "'" !== $apos ) {
175 $dynamic[ '/(?<!' . $spaces . ')\'(?!\Z|[.,:;!?"\'(){}[\]\-]|&[lg]t;|' . $spaces . ')/' ] = $apos_flag;
176 }
177
178 $dynamic_characters['apos'] = array_keys( $dynamic );
179 $dynamic_replacements['apos'] = array_values( $dynamic );
180 $dynamic = array();
181
182 // Quoted Numbers like "42"
183 if ( '"' !== $opening_quote && '"' !== $closing_quote ) {
184 $dynamic[ '/(?<=\A|' . $spaces . ')"(\d[.,\d]*)"/' ] = $open_q_flag . '$1' . $closing_quote;
185 }
186
187 // Double quote at start, or preceded by (, {, <, [, -, or spaces, and not followed by spaces.
188 if ( '"' !== $opening_quote ) {
189 $dynamic[ '/(?<=\A|[([{\-]|<|' . $spaces . ')"(?!' . $spaces . ')/' ] = $open_q_flag;
190 }
191
192 $dynamic_characters['quote'] = array_keys( $dynamic );
193 $dynamic_replacements['quote'] = array_values( $dynamic );
194 $dynamic = array();
195
196 // Dashes and spaces
197 $dynamic[ '/---/' ] = $em_dash;
198 $dynamic[ '/(?<=^|' . $spaces . ')--(?=$|' . $spaces . ')/' ] = $em_dash;
199 $dynamic[ '/(?<!xn)--/' ] = $en_dash;
200 $dynamic[ '/(?<=^|' . $spaces . ')-(?=$|' . $spaces . ')/' ] = $en_dash;
201
202 $dynamic_characters['dash'] = array_keys( $dynamic );
203 $dynamic_replacements['dash'] = array_values( $dynamic );
204 }
205
206 // Must do this every time in case plugins use these filters in a context sensitive manner
207 /**
208 * Filters the list of HTML elements not to texturize.
209 *
210 * @since 2.8.0
211 *
212 * @param array $default_no_texturize_tags An array of HTML element names.
213 */
214 $no_texturize_tags = apply_filters( 'no_texturize_tags', $default_no_texturize_tags );
215 /**
216 * Filters the list of shortcodes not to texturize.
217 *
218 * @since 2.8.0
219 *
220 * @param array $default_no_texturize_shortcodes An array of shortcode names.
221 */
222 $no_texturize_shortcodes = apply_filters( 'no_texturize_shortcodes', $default_no_texturize_shortcodes );
223
224 $no_texturize_tags_stack = array();
225 $no_texturize_shortcodes_stack = array();
226
227 // Look for shortcodes and HTML elements.
228
229 preg_match_all( '@\[/?([^<>&/\[\]\x00-\x20=]++)@', $text, $matches );
230 $tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] );
231 $found_shortcodes = ! empty( $tagnames );
232 $shortcode_regex = $found_shortcodes ? _get_wptexturize_shortcode_regex( $tagnames ) : '';
233 $regex = _get_wptexturize_split_regex( $shortcode_regex );
234
235 $textarr = preg_split( $regex, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
236
237 foreach ( $textarr as &$curl ) {
238 // Only call _wptexturize_pushpop_element if $curl is a delimiter.
239 $first = $curl[0];
240 if ( '<' === $first ) {
241 if ( '<!--' === substr( $curl, 0, 4 ) ) {
242 // This is an HTML comment delimiter.
243 continue;
244 } else {
245 // This is an HTML element delimiter.
246
247 // Replace each & with & unless it already looks like an entity.
248 $curl = preg_replace( '/&(?!#(?:\d+|x[a-f0-9]+);|[a-z1-4]{1,8};)/i', '&', $curl );
249
250 _wptexturize_pushpop_element( $curl, $no_texturize_tags_stack, $no_texturize_tags );
251 }
252
253 } elseif ( '' === trim( $curl ) ) {
254 // This is a newline between delimiters. Performance improves when we check this.
255 continue;
256
257 } elseif ( '[' === $first && $found_shortcodes && 1 === preg_match( '/^' . $shortcode_regex . '$/', $curl ) ) {
258 // This is a shortcode delimiter.
259
260 if ( '[[' !== substr( $curl, 0, 2 ) && ']]' !== substr( $curl, -2 ) ) {
261 // Looks like a normal shortcode.
262 _wptexturize_pushpop_element( $curl, $no_texturize_shortcodes_stack, $no_texturize_shortcodes );
263 } else {
264 // Looks like an escaped shortcode.
265 continue;
266 }
267
268 } elseif ( empty( $no_texturize_shortcodes_stack ) && empty( $no_texturize_tags_stack ) ) {
269 // This is neither a delimiter, nor is this content inside of no_texturize pairs. Do texturize.
270
271 $curl = str_replace( $static_characters, $static_replacements, $curl );
272
273 if ( false !== strpos( $curl, "'" ) ) {
274 $curl = preg_replace( $dynamic_characters['apos'], $dynamic_replacements['apos'], $curl );
275 $curl = wptexturize_primes( $curl, "'", $prime, $open_sq_flag, $closing_single_quote );
276 $curl = str_replace( $apos_flag, $apos, $curl );
277 $curl = str_replace( $open_sq_flag, $opening_single_quote, $curl );
278 }
279 if ( false !== strpos( $curl, '"' ) ) {
280 $curl = preg_replace( $dynamic_characters['quote'], $dynamic_replacements['quote'], $curl );
281 $curl = wptexturize_primes( $curl, '"', $double_prime, $open_q_flag, $closing_quote );
282 $curl = str_replace( $open_q_flag, $opening_quote, $curl );
283 }
284 if ( false !== strpos( $curl, '-' ) ) {
285 $curl = preg_replace( $dynamic_characters['dash'], $dynamic_replacements['dash'], $curl );
286 }
287
288 // 9x9 (times), but never 0x9999
289 if ( 1 === preg_match( '/(?<=\d)x\d/', $curl ) ) {
290 // Searching for a digit is 10 times more expensive than for the x, so we avoid doing this one!
291 $curl = preg_replace( '/\b(\d(?(?<=0)[\d\.,]+|[\d\.,]*))x(\d[\d\.,]*)\b/', '$1×$2', $curl );
292 }
293
294 // Replace each & with & unless it already looks like an entity.
295 $curl = preg_replace( '/&(?!#(?:\d+|x[a-f0-9]+);|[a-z1-4]{1,8};)/i', '&', $curl );
296 }
297 }
298
299 return implode( '', $textarr );
300}
301
302/**
303 * Implements a logic tree to determine whether or not "7'." represents seven feet,
304 * then converts the special char into either a prime char or a closing quote char.
305 *
306 * @since 4.3.0
307 *
308 * @param string $haystack The plain text to be searched.
309 * @param string $needle The character to search for such as ' or ".
310 * @param string $prime The prime char to use for replacement.
311 * @param string $open_quote The opening quote char. Opening quote replacement must be
312 * accomplished already.
313 * @param string $close_quote The closing quote char to use for replacement.
314 * @return string The $haystack value after primes and quotes replacements.
315 */
316function wptexturize_primes( $haystack, $needle, $prime, $open_quote, $close_quote ) {
317 $spaces = wp_spaces_regexp();
318 $flag = '<!--wp-prime-or-quote-->';
319 $quote_pattern = "/$needle(?=\\Z|[.,:;!?)}\\-\\]]|>|" . $spaces . ")/";
320 $prime_pattern = "/(?<=\\d)$needle/";
321 $flag_after_digit = "/(?<=\\d)$flag/";
322 $flag_no_digit = "/(?<!\\d)$flag/";
323
324 $sentences = explode( $open_quote, $haystack );
325
326 foreach ( $sentences as $key => &$sentence ) {
327 if ( false === strpos( $sentence, $needle ) ) {
328 continue;
329 } elseif ( 0 !== $key && 0 === substr_count( $sentence, $close_quote ) ) {
330 $sentence = preg_replace( $quote_pattern, $flag, $sentence, -1, $count );
331 if ( $count > 1 ) {
332 // This sentence appears to have multiple closing quotes. Attempt Vulcan logic.
333 $sentence = preg_replace( $flag_no_digit, $close_quote, $sentence, -1, $count2 );
334 if ( 0 === $count2 ) {
335 // Try looking for a quote followed by a period.
336 $count2 = substr_count( $sentence, "$flag." );
337 if ( $count2 > 0 ) {
338 // Assume the rightmost quote-period match is the end of quotation.
339 $pos = strrpos( $sentence, "$flag." );
340 } else {
341 // When all else fails, make the rightmost candidate a closing quote.
342 // This is most likely to be problematic in the context of bug #18549.
343 $pos = strrpos( $sentence, $flag );
344 }
345 $sentence = substr_replace( $sentence, $close_quote, $pos, strlen( $flag ) );
346 }
347 // Use conventional replacement on any remaining primes and quotes.
348 $sentence = preg_replace( $prime_pattern, $prime, $sentence );
349 $sentence = preg_replace( $flag_after_digit, $prime, $sentence );
350 $sentence = str_replace( $flag, $close_quote, $sentence );
351 } elseif ( 1 == $count ) {
352 // Found only one closing quote candidate, so give it priority over primes.
353 $sentence = str_replace( $flag, $close_quote, $sentence );
354 $sentence = preg_replace( $prime_pattern, $prime, $sentence );
355 } else {
356 // No closing quotes found. Just run primes pattern.
357 $sentence = preg_replace( $prime_pattern, $prime, $sentence );
358 }
359 } else {
360 $sentence = preg_replace( $prime_pattern, $prime, $sentence );
361 $sentence = preg_replace( $quote_pattern, $close_quote, $sentence );
362 }
363 if ( '"' == $needle && false !== strpos( $sentence, '"' ) ) {
364 $sentence = str_replace( '"', $close_quote, $sentence );
365 }
366 }
367
368 return implode( $open_quote, $sentences );
369}
370
371/**
372 * Search for disabled element tags. Push element to stack on tag open and pop
373 * on tag close.
374 *
375 * Assumes first char of $text is tag opening and last char is tag closing.
376 * Assumes second char of $text is optionally '/' to indicate closing as in </html>.
377 *
378 * @since 2.9.0
379 * @access private
380 *
381 * @param string $text Text to check. Must be a tag like `<html>` or `[shortcode]`.
382 * @param array $stack List of open tag elements.
383 * @param array $disabled_elements The tag names to match against. Spaces are not allowed in tag names.
384 */
385function _wptexturize_pushpop_element( $text, &$stack, $disabled_elements ) {
386 // Is it an opening tag or closing tag?
387 if ( isset( $text[1] ) && '/' !== $text[1] ) {
388 $opening_tag = true;
389 $name_offset = 1;
390 } elseif ( 0 == count( $stack ) ) {
391 // Stack is empty. Just stop.
392 return;
393 } else {
394 $opening_tag = false;
395 $name_offset = 2;
396 }
397
398 // Parse out the tag name.
399 $space = strpos( $text, ' ' );
400 if ( false === $space ) {
401 $space = -1;
402 } else {
403 $space -= $name_offset;
404 }
405 $tag = substr( $text, $name_offset, $space );
406
407 // Handle disabled tags.
408 if ( in_array( $tag, $disabled_elements ) ) {
409 if ( $opening_tag ) {
410 /*
411 * This disables texturize until we find a closing tag of our type
412 * (e.g. <pre>) even if there was invalid nesting before that
413 *
414 * Example: in the case <pre>sadsadasd</code>"baba"</pre>
415 * "baba" won't be texturize
416 */
417
418 array_push( $stack, $tag );
419 } elseif ( end( $stack ) == $tag ) {
420 array_pop( $stack );
421 }
422 }
423}
424
425/**
426 * Replaces double line-breaks with paragraph elements.
427 *
428 * A group of regex replaces used to identify text formatted with newlines and
429 * replace double line-breaks with HTML paragraph tags. The remaining line-breaks
430 * after conversion become <<br />> tags, unless $br is set to '0' or 'false'.
431 *
432 * @since 0.71
433 *
434 * @param string $pee The text which has to be formatted.
435 * @param bool $br Optional. If set, this will convert all remaining line-breaks
436 * after paragraphing. Default true.
437 * @return string Text which has been converted into correct paragraph tags.
438 */
439function wpautop( $pee, $br = true ) {
440 $pre_tags = array();
441
442 if ( trim($pee) === '' )
443 return '';
444
445 // Just to make things a little easier, pad the end.
446 $pee = $pee . "\n";
447
448 /*
449 * Pre tags shouldn't be touched by autop.
450 * Replace pre tags with placeholders and bring them back after autop.
451 */
452 if ( strpos($pee, '<pre') !== false ) {
453 $pee_parts = explode( '</pre>', $pee );
454 $last_pee = array_pop($pee_parts);
455 $pee = '';
456 $i = 0;
457
458 foreach ( $pee_parts as $pee_part ) {
459 $start = strpos($pee_part, '<pre');
460
461 // Malformed html?
462 if ( $start === false ) {
463 $pee .= $pee_part;
464 continue;
465 }
466
467 $name = "<pre wp-pre-tag-$i></pre>";
468 $pre_tags[$name] = substr( $pee_part, $start ) . '</pre>';
469
470 $pee .= substr( $pee_part, 0, $start ) . $name;
471 $i++;
472 }
473
474 $pee .= $last_pee;
475 }
476 // Change multiple <br>s into two line breaks, which will turn into paragraphs.
477 $pee = preg_replace('|<br\s*/?>\s*<br\s*/?>|', "\n\n", $pee);
478
479 $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)';
480
481 // Add a double line break above block-level opening tags.
482 $pee = preg_replace('!(<' . $allblocks . '[\s/>])!', "\n\n$1", $pee);
483
484 // Add a double line break below block-level closing tags.
485 $pee = preg_replace('!(</' . $allblocks . '>)!', "$1\n\n", $pee);
486
487 // Standardize newline characters to "\n".
488 $pee = str_replace(array("\r\n", "\r"), "\n", $pee);
489
490 // Find newlines in all elements and add placeholders.
491 $pee = wp_replace_in_html_tags( $pee, array( "\n" => " <!-- wpnl --> " ) );
492
493 // Collapse line breaks before and after <option> elements so they don't get autop'd.
494 if ( strpos( $pee, '<option' ) !== false ) {
495 $pee = preg_replace( '|\s*<option|', '<option', $pee );
496 $pee = preg_replace( '|</option>\s*|', '</option>', $pee );
497 }
498
499 /*
500 * Collapse line breaks inside <object> elements, before <param> and <embed> elements
501 * so they don't get autop'd.
502 */
503 if ( strpos( $pee, '</object>' ) !== false ) {
504 $pee = preg_replace( '|(<object[^>]*>)\s*|', '$1', $pee );
505 $pee = preg_replace( '|\s*</object>|', '</object>', $pee );
506 $pee = preg_replace( '%\s*(</?(?:param|embed)[^>]*>)\s*%', '$1', $pee );
507 }
508
509 /*
510 * Collapse line breaks inside <audio> and <video> elements,
511 * before and after <source> and <track> elements.
512 */
513 if ( strpos( $pee, '<source' ) !== false || strpos( $pee, '<track' ) !== false ) {
514 $pee = preg_replace( '%([<\[](?:audio|video)[^>\]]*[>\]])\s*%', '$1', $pee );
515 $pee = preg_replace( '%\s*([<\[]/(?:audio|video)[>\]])%', '$1', $pee );
516 $pee = preg_replace( '%\s*(<(?:source|track)[^>]*>)\s*%', '$1', $pee );
517 }
518
519 // Collapse line breaks before and after <figcaption> elements.
520 if ( strpos( $pee, '<figcaption' ) !== false ) {
521 $pee = preg_replace( '|\s*(<figcaption[^>]*>)|', '$1', $pee );
522 $pee = preg_replace( '|</figcaption>\s*|', '</figcaption>', $pee );
523 }
524
525 // Remove more than two contiguous line breaks.
526 $pee = preg_replace("/\n\n+/", "\n\n", $pee);
527
528 // Split up the contents into an array of strings, separated by double line breaks.
529 $pees = preg_split('/\n\s*\n/', $pee, -1, PREG_SPLIT_NO_EMPTY);
530
531 // Reset $pee prior to rebuilding.
532 $pee = '';
533
534 // Rebuild the content as a string, wrapping every bit with a <p>.
535 foreach ( $pees as $tinkle ) {
536 $pee .= '<p>' . trim($tinkle, "\n") . "</p>\n";
537 }
538
539 // Under certain strange conditions it could create a P of entirely whitespace.
540 $pee = preg_replace('|<p>\s*</p>|', '', $pee);
541
542 // Add a closing <p> inside <div>, <address>, or <form> tag if missing.
543 $pee = preg_replace('!<p>([^<]+)</(div|address|form)>!', "<p>$1</p></$2>", $pee);
544
545 // If an opening or closing block element tag is wrapped in a <p>, unwrap it.
546 $pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee);
547
548 // In some cases <li> may get wrapped in <p>, fix them.
549 $pee = preg_replace("|<p>(<li.+?)</p>|", "$1", $pee);
550
551 // If a <blockquote> is wrapped with a <p>, move it inside the <blockquote>.
552 $pee = preg_replace('|<p><blockquote([^>]*)>|i', "<blockquote$1><p>", $pee);
553 $pee = str_replace('</blockquote></p>', '</p></blockquote>', $pee);
554
555 // If an opening or closing block element tag is preceded by an opening <p> tag, remove it.
556 $pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)!', "$1", $pee);
557
558 // If an opening or closing block element tag is followed by a closing <p> tag, remove it.
559 $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee);
560
561 // Optionally insert line breaks.
562 if ( $br ) {
563 // Replace newlines that shouldn't be touched with a placeholder.
564 $pee = preg_replace_callback('/<(script|style).*?<\/\\1>/s', '_autop_newline_preservation_helper', $pee);
565
566 // Normalize <br>
567 $pee = str_replace( array( '<br>', '<br/>' ), '<br />', $pee );
568
569 // Replace any new line characters that aren't preceded by a <br /> with a <br />.
570 $pee = preg_replace('|(?<!<br />)\s*\n|', "<br />\n", $pee);
571
572 // Replace newline placeholders with newlines.
573 $pee = str_replace('<WPPreserveNewline />', "\n", $pee);
574 }
575
576 // If a <br /> tag is after an opening or closing block tag, remove it.
577 $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*<br />!', "$1", $pee);
578
579 // If a <br /> tag is before a subset of opening or closing block tags, remove it.
580 $pee = preg_replace('!<br />(\s*</?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)!', '$1', $pee);
581 $pee = preg_replace( "|\n</p>$|", '</p>', $pee );
582
583 // Replace placeholder <pre> tags with their original content.
584 if ( !empty($pre_tags) )
585 $pee = str_replace(array_keys($pre_tags), array_values($pre_tags), $pee);
586
587 // Restore newlines in all elements.
588 if ( false !== strpos( $pee, '<!-- wpnl -->' ) ) {
589 $pee = str_replace( array( ' <!-- wpnl --> ', '<!-- wpnl -->' ), "\n", $pee );
590 }
591
592 return $pee;
593}
594
595/**
596 * Separate HTML elements and comments from the text.
597 *
598 * @since 4.2.4
599 *
600 * @param string $input The text which has to be formatted.
601 * @return array The formatted text.
602 */
603function wp_html_split( $input ) {
604 return preg_split( get_html_split_regex(), $input, -1, PREG_SPLIT_DELIM_CAPTURE );
605}
606
607/**
608 * Retrieve the regular expression for an HTML element.
609 *
610 * @since 4.4.0
611 *
612 * @staticvar string $regex
613 *
614 * @return string The regular expression
615 */
616function get_html_split_regex() {
617 static $regex;
618
619 if ( ! isset( $regex ) ) {
620 $comments =
621 '!' // Start of comment, after the <.
622 . '(?:' // Unroll the loop: Consume everything until --> is found.
623 . '-(?!->)' // Dash not followed by end of comment.
624 . '[^\-]*+' // Consume non-dashes.
625 . ')*+' // Loop possessively.
626 . '(?:-->)?'; // End of comment. If not found, match all input.
627
628 $cdata =
629 '!\[CDATA\[' // Start of comment, after the <.
630 . '[^\]]*+' // Consume non-].
631 . '(?:' // Unroll the loop: Consume everything until ]]> is found.
632 . '](?!]>)' // One ] not followed by end of comment.
633 . '[^\]]*+' // Consume non-].
634 . ')*+' // Loop possessively.
635 . '(?:]]>)?'; // End of comment. If not found, match all input.
636
637 $escaped =
638 '(?=' // Is the element escaped?
639 . '!--'
640 . '|'
641 . '!\[CDATA\['
642 . ')'
643 . '(?(?=!-)' // If yes, which type?
644 . $comments
645 . '|'
646 . $cdata
647 . ')';
648
649 $regex =
650 '/(' // Capture the entire match.
651 . '<' // Find start of element.
652 . '(?' // Conditional expression follows.
653 . $escaped // Find end of escaped element.
654 . '|' // ... else ...
655 . '[^>]*>?' // Find end of normal element.
656 . ')'
657 . ')/';
658 }
659
660 return $regex;
661}
662
663/**
664 * Retrieve the combined regular expression for HTML and shortcodes.
665 *
666 * @access private
667 * @ignore
668 * @internal This function will be removed in 4.5.0 per Shortcode API Roadmap.
669 * @since 4.4.0
670 *
671 * @staticvar string $html_regex
672 *
673 * @param string $shortcode_regex The result from _get_wptexturize_shortcode_regex(). Optional.
674 * @return string The regular expression
675 */
676function _get_wptexturize_split_regex( $shortcode_regex = '' ) {
677 static $html_regex;
678
679 if ( ! isset( $html_regex ) ) {
680 $comment_regex =
681 '!' // Start of comment, after the <.
682 . '(?:' // Unroll the loop: Consume everything until --> is found.
683 . '-(?!->)' // Dash not followed by end of comment.
684 . '[^\-]*+' // Consume non-dashes.
685 . ')*+' // Loop possessively.
686 . '(?:-->)?'; // End of comment. If not found, match all input.
687
688 $html_regex = // Needs replaced with wp_html_split() per Shortcode API Roadmap.
689 '<' // Find start of element.
690 . '(?(?=!--)' // Is this a comment?
691 . $comment_regex // Find end of comment.
692 . '|'
693 . '[^>]*>?' // Find end of element. If not found, match all input.
694 . ')';
695 }
696
697 if ( empty( $shortcode_regex ) ) {
698 $regex = '/(' . $html_regex . ')/';
699 } else {
700 $regex = '/(' . $html_regex . '|' . $shortcode_regex . ')/';
701 }
702
703 return $regex;
704}
705
706/**
707 * Retrieve the regular expression for shortcodes.
708 *
709 * @access private
710 * @ignore
711 * @internal This function will be removed in 4.5.0 per Shortcode API Roadmap.
712 * @since 4.4.0
713 *
714 * @param array $tagnames List of shortcodes to find.
715 * @return string The regular expression
716 */
717function _get_wptexturize_shortcode_regex( $tagnames ) {
718 $tagregexp = join( '|', array_map( 'preg_quote', $tagnames ) );
719 $tagregexp = "(?:$tagregexp)(?=[\\s\\]\\/])"; // Excerpt of get_shortcode_regex().
720 $regex =
721 '\[' // Find start of shortcode.
722 . '[\/\[]?' // Shortcodes may begin with [/ or [[
723 . $tagregexp // Only match registered shortcodes, because performance.
724 . '(?:'
725 . '[^\[\]<>]+' // Shortcodes do not contain other shortcodes. Quantifier critical.
726 . '|'
727 . '<[^\[\]>]*>' // HTML elements permitted. Prevents matching ] before >.
728 . ')*+' // Possessive critical.
729 . '\]' // Find end of shortcode.
730 . '\]?'; // Shortcodes may end with ]]
731
732 return $regex;
733}
734
735/**
736 * Replace characters or phrases within HTML elements only.
737 *
738 * @since 4.2.3
739 *
740 * @param string $haystack The text which has to be formatted.
741 * @param array $replace_pairs In the form array('from' => 'to', ...).
742 * @return string The formatted text.
743 */
744function wp_replace_in_html_tags( $haystack, $replace_pairs ) {
745 // Find all elements.
746 $textarr = wp_html_split( $haystack );
747 $changed = false;
748
749 // Optimize when searching for one item.
750 if ( 1 === count( $replace_pairs ) ) {
751 // Extract $needle and $replace.
752 foreach ( $replace_pairs as $needle => $replace );
753
754 // Loop through delimiters (elements) only.
755 for ( $i = 1, $c = count( $textarr ); $i < $c; $i += 2 ) {
756 if ( false !== strpos( $textarr[$i], $needle ) ) {
757 $textarr[$i] = str_replace( $needle, $replace, $textarr[$i] );
758 $changed = true;
759 }
760 }
761 } else {
762 // Extract all $needles.
763 $needles = array_keys( $replace_pairs );
764
765 // Loop through delimiters (elements) only.
766 for ( $i = 1, $c = count( $textarr ); $i < $c; $i += 2 ) {
767 foreach ( $needles as $needle ) {
768 if ( false !== strpos( $textarr[$i], $needle ) ) {
769 $textarr[$i] = strtr( $textarr[$i], $replace_pairs );
770 $changed = true;
771 // After one strtr() break out of the foreach loop and look at next element.
772 break;
773 }
774 }
775 }
776 }
777
778 if ( $changed ) {
779 $haystack = implode( $textarr );
780 }
781
782 return $haystack;
783}
784
785/**
786 * Newline preservation help function for wpautop
787 *
788 * @since 3.1.0
789 * @access private
790 *
791 * @param array $matches preg_replace_callback matches array
792 * @return string
793 */
794function _autop_newline_preservation_helper( $matches ) {
795 return str_replace( "\n", "<WPPreserveNewline />", $matches[0] );
796}
797
798/**
799 * Don't auto-p wrap shortcodes that stand alone
800 *
801 * Ensures that shortcodes are not wrapped in `<p>...</p>`.
802 *
803 * @since 2.9.0
804 *
805 * @global array $shortcode_tags
806 *
807 * @param string $pee The content.
808 * @return string The filtered content.
809 */
810function shortcode_unautop( $pee ) {
811 global $shortcode_tags;
812
813 if ( empty( $shortcode_tags ) || !is_array( $shortcode_tags ) ) {
814 return $pee;
815 }
816
817 $tagregexp = join( '|', array_map( 'preg_quote', array_keys( $shortcode_tags ) ) );
818 $spaces = wp_spaces_regexp();
819
820 $pattern =
821 '/'
822 . '<p>' // Opening paragraph
823 . '(?:' . $spaces . ')*+' // Optional leading whitespace
824 . '(' // 1: The shortcode
825 . '\\[' // Opening bracket
826 . "($tagregexp)" // 2: Shortcode name
827 . '(?![\\w-])' // Not followed by word character or hyphen
828 // Unroll the loop: Inside the opening shortcode tag
829 . '[^\\]\\/]*' // Not a closing bracket or forward slash
830 . '(?:'
831 . '\\/(?!\\])' // A forward slash not followed by a closing bracket
832 . '[^\\]\\/]*' // Not a closing bracket or forward slash
833 . ')*?'
834 . '(?:'
835 . '\\/\\]' // Self closing tag and closing bracket
836 . '|'
837 . '\\]' // Closing bracket
838 . '(?:' // Unroll the loop: Optionally, anything between the opening and closing shortcode tags
839 . '[^\\[]*+' // Not an opening bracket
840 . '(?:'
841 . '\\[(?!\\/\\2\\])' // An opening bracket not followed by the closing shortcode tag
842 . '[^\\[]*+' // Not an opening bracket
843 . ')*+'
844 . '\\[\\/\\2\\]' // Closing shortcode tag
845 . ')?'
846 . ')'
847 . ')'
848 . '(?:' . $spaces . ')*+' // optional trailing whitespace
849 . '<\\/p>' // closing paragraph
850 . '/';
851
852 return preg_replace( $pattern, '$1', $pee );
853}
854
855/**
856 * Checks to see if a string is utf8 encoded.
857 *
858 * NOTE: This function checks for 5-Byte sequences, UTF8
859 * has Bytes Sequences with a maximum length of 4.
860 *
861 * @author bmorel at ssi dot fr (modified)
862 * @since 1.2.1
863 *
864 * @param string $str The string to be checked
865 * @return bool True if $str fits a UTF-8 model, false otherwise.
866 */
867function seems_utf8( $str ) {
868 mbstring_binary_safe_encoding();
869 $length = strlen($str);
870 reset_mbstring_encoding();
871 for ($i=0; $i < $length; $i++) {
872 $c = ord($str[$i]);
873 if ($c < 0x80) $n = 0; // 0bbbbbbb
874 elseif (($c & 0xE0) == 0xC0) $n=1; // 110bbbbb
875 elseif (($c & 0xF0) == 0xE0) $n=2; // 1110bbbb
876 elseif (($c & 0xF8) == 0xF0) $n=3; // 11110bbb
877 elseif (($c & 0xFC) == 0xF8) $n=4; // 111110bb
878 elseif (($c & 0xFE) == 0xFC) $n=5; // 1111110b
879 else return false; // Does not match any model
880 for ($j=0; $j<$n; $j++) { // n bytes matching 10bbbbbb follow ?
881 if ((++$i == $length) || ((ord($str[$i]) & 0xC0) != 0x80))
882 return false;
883 }
884 }
885 return true;
886}
887
888/**
889 * Converts a number of special characters into their HTML entities.
890 *
891 * Specifically deals with: &, <, >, ", and '.
892 *
893 * $quote_style can be set to ENT_COMPAT to encode " to
894 * ", or ENT_QUOTES to do both. Default is ENT_NOQUOTES where no quotes are encoded.
895 *
896 * @since 1.2.2
897 * @access private
898 *
899 * @staticvar string $_charset
900 *
901 * @param string $string The text which is to be encoded.
902 * @param int|string $quote_style Optional. Converts double quotes if set to ENT_COMPAT,
903 * both single and double if set to ENT_QUOTES or none if set to ENT_NOQUOTES.
904 * Also compatible with old values; converting single quotes if set to 'single',
905 * double if set to 'double' or both if otherwise set.
906 * Default is ENT_NOQUOTES.
907 * @param string $charset Optional. The character encoding of the string. Default is false.
908 * @param bool $double_encode Optional. Whether to encode existing html entities. Default is false.
909 * @return string The encoded text with HTML entities.
910 */
911function _wp_specialchars( $string, $quote_style = ENT_NOQUOTES, $charset = false, $double_encode = false ) {
912 $string = (string) $string;
913
914 if ( 0 === strlen( $string ) )
915 return '';
916
917 // Don't bother if there are no specialchars - saves some processing
918 if ( ! preg_match( '/[&<>"\']/', $string ) )
919 return $string;
920
921 // Account for the previous behaviour of the function when the $quote_style is not an accepted value
922 if ( empty( $quote_style ) )
923 $quote_style = ENT_NOQUOTES;
924 elseif ( ! in_array( $quote_style, array( 0, 2, 3, 'single', 'double' ), true ) )
925 $quote_style = ENT_QUOTES;
926
927 // Store the site charset as a static to avoid multiple calls to wp_load_alloptions()
928 if ( ! $charset ) {
929 static $_charset = null;
930 if ( ! isset( $_charset ) ) {
931 $alloptions = wp_load_alloptions();
932 $_charset = isset( $alloptions['blog_charset'] ) ? $alloptions['blog_charset'] : '';
933 }
934 $charset = $_charset;
935 }
936
937 if ( in_array( $charset, array( 'utf8', 'utf-8', 'UTF8' ) ) )
938 $charset = 'UTF-8';
939
940 $_quote_style = $quote_style;
941
942 if ( $quote_style === 'double' ) {
943 $quote_style = ENT_COMPAT;
944 $_quote_style = ENT_COMPAT;
945 } elseif ( $quote_style === 'single' ) {
946 $quote_style = ENT_NOQUOTES;
947 }
948
949 if ( ! $double_encode ) {
950 // Guarantee every &entity; is valid, convert &garbage; into &garbage;
951 // This is required for PHP < 5.4.0 because ENT_HTML401 flag is unavailable.
952 $string = wp_kses_normalize_entities( $string );
953 }
954
955 $string = @htmlspecialchars( $string, $quote_style, $charset, $double_encode );
956
957 // Back-compat.
958 if ( 'single' === $_quote_style )
959 $string = str_replace( "'", ''', $string );
960
961 return $string;
962}
963
964/**
965 * Converts a number of HTML entities into their special characters.
966 *
967 * Specifically deals with: &, <, >, ", and '.
968 *
969 * $quote_style can be set to ENT_COMPAT to decode " entities,
970 * or ENT_QUOTES to do both " and '. Default is ENT_NOQUOTES where no quotes are decoded.
971 *
972 * @since 2.8.0
973 *
974 * @param string $string The text which is to be decoded.
975 * @param string|int $quote_style Optional. Converts double quotes if set to ENT_COMPAT,
976 * both single and double if set to ENT_QUOTES or
977 * none if set to ENT_NOQUOTES.
978 * Also compatible with old _wp_specialchars() values;
979 * converting single quotes if set to 'single',
980 * double if set to 'double' or both if otherwise set.
981 * Default is ENT_NOQUOTES.
982 * @return string The decoded text without HTML entities.
983 */
984function wp_specialchars_decode( $string, $quote_style = ENT_NOQUOTES ) {
985 $string = (string) $string;
986
987 if ( 0 === strlen( $string ) ) {
988 return '';
989 }
990
991 // Don't bother if there are no entities - saves a lot of processing
992 if ( strpos( $string, '&' ) === false ) {
993 return $string;
994 }
995
996 // Match the previous behaviour of _wp_specialchars() when the $quote_style is not an accepted value
997 if ( empty( $quote_style ) ) {
998 $quote_style = ENT_NOQUOTES;
999 } elseif ( !in_array( $quote_style, array( 0, 2, 3, 'single', 'double' ), true ) ) {
1000 $quote_style = ENT_QUOTES;
1001 }
1002
1003 // More complete than get_html_translation_table( HTML_SPECIALCHARS )
1004 $single = array( ''' => '\'', ''' => '\'' );
1005 $single_preg = array( '/�*39;/' => ''', '/�*27;/i' => ''' );
1006 $double = array( '"' => '"', '"' => '"', '"' => '"' );
1007 $double_preg = array( '/�*34;/' => '"', '/�*22;/i' => '"' );
1008 $others = array( '<' => '<', '<' => '<', '>' => '>', '>' => '>', '&' => '&', '&' => '&', '&' => '&' );
1009 $others_preg = array( '/�*60;/' => '<', '/�*62;/' => '>', '/�*38;/' => '&', '/�*26;/i' => '&' );
1010
1011 if ( $quote_style === ENT_QUOTES ) {
1012 $translation = array_merge( $single, $double, $others );
1013 $translation_preg = array_merge( $single_preg, $double_preg, $others_preg );
1014 } elseif ( $quote_style === ENT_COMPAT || $quote_style === 'double' ) {
1015 $translation = array_merge( $double, $others );
1016 $translation_preg = array_merge( $double_preg, $others_preg );
1017 } elseif ( $quote_style === 'single' ) {
1018 $translation = array_merge( $single, $others );
1019 $translation_preg = array_merge( $single_preg, $others_preg );
1020 } elseif ( $quote_style === ENT_NOQUOTES ) {
1021 $translation = $others;
1022 $translation_preg = $others_preg;
1023 }
1024
1025 // Remove zero padding on numeric entities
1026 $string = preg_replace( array_keys( $translation_preg ), array_values( $translation_preg ), $string );
1027
1028 // Replace characters according to translation table
1029 return strtr( $string, $translation );
1030}
1031
1032/**
1033 * Checks for invalid UTF8 in a string.
1034 *
1035 * @since 2.8.0
1036 *
1037 * @staticvar bool $is_utf8
1038 * @staticvar bool $utf8_pcre
1039 *
1040 * @param string $string The text which is to be checked.
1041 * @param bool $strip Optional. Whether to attempt to strip out invalid UTF8. Default is false.
1042 * @return string The checked text.
1043 */
1044function wp_check_invalid_utf8( $string, $strip = false ) {
1045 $string = (string) $string;
1046
1047 if ( 0 === strlen( $string ) ) {
1048 return '';
1049 }
1050
1051 // Store the site charset as a static to avoid multiple calls to get_option()
1052 static $is_utf8 = null;
1053 if ( ! isset( $is_utf8 ) ) {
1054 $is_utf8 = in_array( get_option( 'blog_charset' ), array( 'utf8', 'utf-8', 'UTF8', 'UTF-8' ) );
1055 }
1056 if ( ! $is_utf8 ) {
1057 return $string;
1058 }
1059
1060 // Check for support for utf8 in the installed PCRE library once and store the result in a static
1061 static $utf8_pcre = null;
1062 if ( ! isset( $utf8_pcre ) ) {
1063 $utf8_pcre = @preg_match( '/^./u', 'a' );
1064 }
1065 // We can't demand utf8 in the PCRE installation, so just return the string in those cases
1066 if ( !$utf8_pcre ) {
1067 return $string;
1068 }
1069
1070 // preg_match fails when it encounters invalid UTF8 in $string
1071 if ( 1 === @preg_match( '/^./us', $string ) ) {
1072 return $string;
1073 }
1074
1075 // Attempt to strip the bad chars if requested (not recommended)
1076 if ( $strip && function_exists( 'iconv' ) ) {
1077 return iconv( 'utf-8', 'utf-8', $string );
1078 }
1079
1080 return '';
1081}
1082
1083/**
1084 * Encode the Unicode values to be used in the URI.
1085 *
1086 * @since 1.5.0
1087 *
1088 * @param string $utf8_string
1089 * @param int $length Max length of the string
1090 * @return string String with Unicode encoded for URI.
1091 */
1092function utf8_uri_encode( $utf8_string, $length = 0 ) {
1093 $unicode = '';
1094 $values = array();
1095 $num_octets = 1;
1096 $unicode_length = 0;
1097
1098 mbstring_binary_safe_encoding();
1099 $string_length = strlen( $utf8_string );
1100 reset_mbstring_encoding();
1101
1102 for ($i = 0; $i < $string_length; $i++ ) {
1103
1104 $value = ord( $utf8_string[ $i ] );
1105
1106 if ( $value < 128 ) {
1107 if ( $length && ( $unicode_length >= $length ) )
1108 break;
1109 $unicode .= chr($value);
1110 $unicode_length++;
1111 } else {
1112 if ( count( $values ) == 0 ) {
1113 if ( $value < 224 ) {
1114 $num_octets = 2;
1115 } elseif ( $value < 240 ) {
1116 $num_octets = 3;
1117 } else {
1118 $num_octets = 4;
1119 }
1120 }
1121
1122 $values[] = $value;
1123
1124 if ( $length && ( $unicode_length + ($num_octets * 3) ) > $length )
1125 break;
1126 if ( count( $values ) == $num_octets ) {
1127 for ( $j = 0; $j < $num_octets; $j++ ) {
1128 $unicode .= '%' . dechex( $values[ $j ] );
1129 }
1130
1131 $unicode_length += $num_octets * 3;
1132
1133 $values = array();
1134 $num_octets = 1;
1135 }
1136 }
1137 }
1138
1139 return $unicode;
1140}
1141
1142/**
1143 * Converts all accent characters to ASCII characters.
1144 *
1145 * If there are no accent characters, then the string given is just returned.
1146 *
1147 * **Accent characters converted:**
1148 *
1149 * Currency signs:
1150 *
1151 * | Code | Glyph | Replacement | Description |
1152 * | -------- | ----- | ----------- | ------------------- |
1153 * | U+00A3 | £ | (empty) | British Pound sign |
1154 * | U+20AC | € | E | Euro sign |
1155 *
1156 * Decompositions for Latin-1 Supplement:
1157 *
1158 * | Code | Glyph | Replacement | Description |
1159 * | ------- | ----- | ----------- | -------------------------------------- |
1160 * | U+00AA | ª | a | Feminine ordinal indicator |
1161 * | U+00BA | º | o | Masculine ordinal indicator |
1162 * | U+00C0 | À | A | Latin capital letter A with grave |
1163 * | U+00C1 | Ã | A | Latin capital letter A with acute |
1164 * | U+00C2 | Â | A | Latin capital letter A with circumflex |
1165 * | U+00C3 | Ã | A | Latin capital letter A with tilde |
1166 * | U+00C4 | Ä | A | Latin capital letter A with diaeresis |
1167 * | U+00C5 | Ã… | A | Latin capital letter A with ring above |
1168 * | U+00C6 | Æ | AE | Latin capital letter AE |
1169 * | U+00C7 | Ç | C | Latin capital letter C with cedilla |
1170 * | U+00C8 | È | E | Latin capital letter E with grave |
1171 * | U+00C9 | É | E | Latin capital letter E with acute |
1172 * | U+00CA | Ê | E | Latin capital letter E with circumflex |
1173 * | U+00CB | Ë | E | Latin capital letter E with diaeresis |
1174 * | U+00CC | Ì | I | Latin capital letter I with grave |
1175 * | U+00CD | Ã | I | Latin capital letter I with acute |
1176 * | U+00CE | ÃŽ | I | Latin capital letter I with circumflex |
1177 * | U+00CF | Ã | I | Latin capital letter I with diaeresis |
1178 * | U+00D0 | Ã | D | Latin capital letter Eth |
1179 * | U+00D1 | Ñ | N | Latin capital letter N with tilde |
1180 * | U+00D2 | Ã’ | O | Latin capital letter O with grave |
1181 * | U+00D3 | Ó | O | Latin capital letter O with acute |
1182 * | U+00D4 | Ô | O | Latin capital letter O with circumflex |
1183 * | U+00D5 | Õ | O | Latin capital letter O with tilde |
1184 * | U+00D6 | Ö | O | Latin capital letter O with diaeresis |
1185 * | U+00D8 | Ø | O | Latin capital letter O with stroke |
1186 * | U+00D9 | Ù | U | Latin capital letter U with grave |
1187 * | U+00DA | Ú | U | Latin capital letter U with acute |
1188 * | U+00DB | Û | U | Latin capital letter U with circumflex |
1189 * | U+00DC | Ü | U | Latin capital letter U with diaeresis |
1190 * | U+00DD | Ã | Y | Latin capital letter Y with acute |
1191 * | U+00DE | Þ | TH | Latin capital letter Thorn |
1192 * | U+00DF | ß | s | Latin small letter sharp s |
1193 * | U+00E0 | Ã | a | Latin small letter a with grave |
1194 * | U+00E1 | á | a | Latin small letter a with acute |
1195 * | U+00E2 | â | a | Latin small letter a with circumflex |
1196 * | U+00E3 | ã | a | Latin small letter a with tilde |
1197 * | U+00E4 | ä | a | Latin small letter a with diaeresis |
1198 * | U+00E5 | å | a | Latin small letter a with ring above |
1199 * | U+00E6 | æ | ae | Latin small letter ae |
1200 * | U+00E7 | ç | c | Latin small letter c with cedilla |
1201 * | U+00E8 | è | e | Latin small letter e with grave |
1202 * | U+00E9 | é | e | Latin small letter e with acute |
1203 * | U+00EA | ê | e | Latin small letter e with circumflex |
1204 * | U+00EB | ë | e | Latin small letter e with diaeresis |
1205 * | U+00EC | ì | i | Latin small letter i with grave |
1206 * | U+00ED | Ã | i | Latin small letter i with acute |
1207 * | U+00EE | î | i | Latin small letter i with circumflex |
1208 * | U+00EF | ï | i | Latin small letter i with diaeresis |
1209 * | U+00F0 | ð | d | Latin small letter Eth |
1210 * | U+00F1 | ñ | n | Latin small letter n with tilde |
1211 * | U+00F2 | ò | o | Latin small letter o with grave |
1212 * | U+00F3 | ó | o | Latin small letter o with acute |
1213 * | U+00F4 | ô | o | Latin small letter o with circumflex |
1214 * | U+00F5 | õ | o | Latin small letter o with tilde |
1215 * | U+00F6 | ö | o | Latin small letter o with diaeresis |
1216 * | U+00F8 | ø | o | Latin small letter o with stroke |
1217 * | U+00F9 | ù | u | Latin small letter u with grave |
1218 * | U+00FA | ú | u | Latin small letter u with acute |
1219 * | U+00FB | û | u | Latin small letter u with circumflex |
1220 * | U+00FC | ü | u | Latin small letter u with diaeresis |
1221 * | U+00FD | ý | y | Latin small letter y with acute |
1222 * | U+00FE | þ | th | Latin small letter Thorn |
1223 * | U+00FF | ÿ | y | Latin small letter y with diaeresis |
1224 *
1225 * Decompositions for Latin Extended-A:
1226 *
1227 * | Code | Glyph | Replacement | Description |
1228 * | ------- | ----- | ----------- | ------------------------------------------------- |
1229 * | U+0100 | Ā | A | Latin capital letter A with macron |
1230 * | U+0101 | Ä | a | Latin small letter a with macron |
1231 * | U+0102 | Ä‚ | A | Latin capital letter A with breve |
1232 * | U+0103 | ă | a | Latin small letter a with breve |
1233 * | U+0104 | Ä„ | A | Latin capital letter A with ogonek |
1234 * | U+0105 | Ä… | a | Latin small letter a with ogonek |
1235 * | U+01006 | Ć | C | Latin capital letter C with acute |
1236 * | U+0107 | ć | c | Latin small letter c with acute |
1237 * | U+0108 | Ĉ | C | Latin capital letter C with circumflex |
1238 * | U+0109 | ĉ | c | Latin small letter c with circumflex |
1239 * | U+010A | ÄŠ | C | Latin capital letter C with dot above |
1240 * | U+010B | Ä‹ | c | Latin small letter c with dot above |
1241 * | U+010C | Č | C | Latin capital letter C with caron |
1242 * | U+010D | Ä | c | Latin small letter c with caron |
1243 * | U+010E | ÄŽ | D | Latin capital letter D with caron |
1244 * | U+010F | Ä | d | Latin small letter d with caron |
1245 * | U+0110 | Ä | D | Latin capital letter D with stroke |
1246 * | U+0111 | Ä‘ | d | Latin small letter d with stroke |
1247 * | U+0112 | Ä’ | E | Latin capital letter E with macron |
1248 * | U+0113 | Ä“ | e | Latin small letter e with macron |
1249 * | U+0114 | Ä” | E | Latin capital letter E with breve |
1250 * | U+0115 | Ä• | e | Latin small letter e with breve |
1251 * | U+0116 | Ä– | E | Latin capital letter E with dot above |
1252 * | U+0117 | Ä— | e | Latin small letter e with dot above |
1253 * | U+0118 | Ę | E | Latin capital letter E with ogonek |
1254 * | U+0119 | Ä™ | e | Latin small letter e with ogonek |
1255 * | U+011A | Äš | E | Latin capital letter E with caron |
1256 * | U+011B | Ä› | e | Latin small letter e with caron |
1257 * | U+011C | Ĝ | G | Latin capital letter G with circumflex |
1258 * | U+011D | Ä | g | Latin small letter g with circumflex |
1259 * | U+011E | Äž | G | Latin capital letter G with breve |
1260 * | U+011F | ÄŸ | g | Latin small letter g with breve |
1261 * | U+0120 | Ä | G | Latin capital letter G with dot above |
1262 * | U+0121 | Ä¡ | g | Latin small letter g with dot above |
1263 * | U+0122 | Ģ | G | Latin capital letter G with cedilla |
1264 * | U+0123 | ģ | g | Latin small letter g with cedilla |
1265 * | U+0124 | Ĥ | H | Latin capital letter H with circumflex |
1266 * | U+0125 | ĥ | h | Latin small letter h with circumflex |
1267 * | U+0126 | Ħ | H | Latin capital letter H with stroke |
1268 * | U+0127 | ħ | h | Latin small letter h with stroke |
1269 * | U+0128 | Ĩ | I | Latin capital letter I with tilde |
1270 * | U+0129 | Ä© | i | Latin small letter i with tilde |
1271 * | U+012A | Ī | I | Latin capital letter I with macron |
1272 * | U+012B | Ä« | i | Latin small letter i with macron |
1273 * | U+012C | Ĭ | I | Latin capital letter I with breve |
1274 * | U+012D | Ä | i | Latin small letter i with breve |
1275 * | U+012E | Ä® | I | Latin capital letter I with ogonek |
1276 * | U+012F | į | i | Latin small letter i with ogonek |
1277 * | U+0130 | İ | I | Latin capital letter I with dot above |
1278 * | U+0131 | ı | i | Latin small letter dotless i |
1279 * | U+0132 | IJ | IJ | Latin capital ligature IJ |
1280 * | U+0133 | ij | ij | Latin small ligature ij |
1281 * | U+0134 | Ä´ | J | Latin capital letter J with circumflex |
1282 * | U+0135 | ĵ | j | Latin small letter j with circumflex |
1283 * | U+0136 | Ķ | K | Latin capital letter K with cedilla |
1284 * | U+0137 | Ä· | k | Latin small letter k with cedilla |
1285 * | U+0138 | ĸ | k | Latin small letter Kra |
1286 * | U+0139 | Ĺ | L | Latin capital letter L with acute |
1287 * | U+013A | ĺ | l | Latin small letter l with acute |
1288 * | U+013B | Ä» | L | Latin capital letter L with cedilla |
1289 * | U+013C | ļ | l | Latin small letter l with cedilla |
1290 * | U+013D | Ľ | L | Latin capital letter L with caron |
1291 * | U+013E | ľ | l | Latin small letter l with caron |
1292 * | U+013F | Ä¿ | L | Latin capital letter L with middle dot |
1293 * | U+0140 | ŀ | l | Latin small letter l with middle dot |
1294 * | U+0141 | Å | L | Latin capital letter L with stroke |
1295 * | U+0142 | Å‚ | l | Latin small letter l with stroke |
1296 * | U+0143 | Ń | N | Latin capital letter N with acute |
1297 * | U+0144 | Å„ | n | Latin small letter N with acute |
1298 * | U+0145 | Å… | N | Latin capital letter N with cedilla |
1299 * | U+0146 | ņ | n | Latin small letter n with cedilla |
1300 * | U+0147 | Ň | N | Latin capital letter N with caron |
1301 * | U+0148 | ň | n | Latin small letter n with caron |
1302 * | U+0149 | ʼn | n | Latin small letter n preceded by apostrophe |
1303 * | U+014A | ÅŠ | N | Latin capital letter Eng |
1304 * | U+014B | Å‹ | n | Latin small letter Eng |
1305 * | U+014C | Ō | O | Latin capital letter O with macron |
1306 * | U+014D | Å | o | Latin small letter o with macron |
1307 * | U+014E | ÅŽ | O | Latin capital letter O with breve |
1308 * | U+014F | Å | o | Latin small letter o with breve |
1309 * | U+0150 | Å | O | Latin capital letter O with double acute |
1310 * | U+0151 | Å‘ | o | Latin small letter o with double acute |
1311 * | U+0152 | Å’ | OE | Latin capital ligature OE |
1312 * | U+0153 | Å“ | oe | Latin small ligature oe |
1313 * | U+0154 | Å” | R | Latin capital letter R with acute |
1314 * | U+0155 | Å• | r | Latin small letter r with acute |
1315 * | U+0156 | Å– | R | Latin capital letter R with cedilla |
1316 * | U+0157 | Å— | r | Latin small letter r with cedilla |
1317 * | U+0158 | Ř | R | Latin capital letter R with caron |
1318 * | U+0159 | Å™ | r | Latin small letter r with caron |
1319 * | U+015A | Åš | S | Latin capital letter S with acute |
1320 * | U+015B | Å› | s | Latin small letter s with acute |
1321 * | U+015C | Ŝ | S | Latin capital letter S with circumflex |
1322 * | U+015D | Å | s | Latin small letter s with circumflex |
1323 * | U+015E | Åž | S | Latin capital letter S with cedilla |
1324 * | U+015F | ÅŸ | s | Latin small letter s with cedilla |
1325 * | U+0160 | Å | S | Latin capital letter S with caron |
1326 * | U+0161 | Å¡ | s | Latin small letter s with caron |
1327 * | U+0162 | Ţ | T | Latin capital letter T with cedilla |
1328 * | U+0163 | ţ | t | Latin small letter t with cedilla |
1329 * | U+0164 | Ť | T | Latin capital letter T with caron |
1330 * | U+0165 | ť | t | Latin small letter t with caron |
1331 * | U+0166 | Ŧ | T | Latin capital letter T with stroke |
1332 * | U+0167 | ŧ | t | Latin small letter t with stroke |
1333 * | U+0168 | Ũ | U | Latin capital letter U with tilde |
1334 * | U+0169 | Å© | u | Latin small letter u with tilde |
1335 * | U+016A | Ū | U | Latin capital letter U with macron |
1336 * | U+016B | Å« | u | Latin small letter u with macron |
1337 * | U+016C | Ŭ | U | Latin capital letter U with breve |
1338 * | U+016D | Å | u | Latin small letter u with breve |
1339 * | U+016E | Å® | U | Latin capital letter U with ring above |
1340 * | U+016F | ů | u | Latin small letter u with ring above |
1341 * | U+0170 | Ű | U | Latin capital letter U with double acute |
1342 * | U+0171 | ű | u | Latin small letter u with double acute |
1343 * | U+0172 | Ų | U | Latin capital letter U with ogonek |
1344 * | U+0173 | ų | u | Latin small letter u with ogonek |
1345 * | U+0174 | Å´ | W | Latin capital letter W with circumflex |
1346 * | U+0175 | ŵ | w | Latin small letter w with circumflex |
1347 * | U+0176 | Ŷ | Y | Latin capital letter Y with circumflex |
1348 * | U+0177 | Å· | y | Latin small letter y with circumflex |
1349 * | U+0178 | Ÿ | Y | Latin capital letter Y with diaeresis |
1350 * | U+0179 | Ź | Z | Latin capital letter Z with acute |
1351 * | U+017A | ź | z | Latin small letter z with acute |
1352 * | U+017B | Å» | Z | Latin capital letter Z with dot above |
1353 * | U+017C | ż | z | Latin small letter z with dot above |
1354 * | U+017D | Ž | Z | Latin capital letter Z with caron |
1355 * | U+017E | ž | z | Latin small letter z with caron |
1356 * | U+017F | Å¿ | s | Latin small letter long s |
1357 * | U+01A0 | Æ | O | Latin capital letter O with horn |
1358 * | U+01A1 | Æ¡ | o | Latin small letter o with horn |
1359 * | U+01AF | Ư | U | Latin capital letter U with horn |
1360 * | U+01B0 | ư | u | Latin small letter u with horn |
1361 * | U+01CD | Ç | A | Latin capital letter A with caron |
1362 * | U+01CE | ÇŽ | a | Latin small letter a with caron |
1363 * | U+01CF | Ç | I | Latin capital letter I with caron |
1364 * | U+01D0 | Ç | i | Latin small letter i with caron |
1365 * | U+01D1 | Ç‘ | O | Latin capital letter O with caron |
1366 * | U+01D2 | Ç’ | o | Latin small letter o with caron |
1367 * | U+01D3 | Ç“ | U | Latin capital letter U with caron |
1368 * | U+01D4 | Ç” | u | Latin small letter u with caron |
1369 * | U+01D5 | Ç• | U | Latin capital letter U with diaeresis and macron |
1370 * | U+01D6 | Ç– | u | Latin small letter u with diaeresis and macron |
1371 * | U+01D7 | Ç— | U | Latin capital letter U with diaeresis and acute |
1372 * | U+01D8 | ǘ | u | Latin small letter u with diaeresis and acute |
1373 * | U+01D9 | Ç™ | U | Latin capital letter U with diaeresis and caron |
1374 * | U+01DA | Çš | u | Latin small letter u with diaeresis and caron |
1375 * | U+01DB | Ç› | U | Latin capital letter U with diaeresis and grave |
1376 * | U+01DC | ǜ | u | Latin small letter u with diaeresis and grave |
1377 *
1378 * Decompositions for Latin Extended-B:
1379 *
1380 * | Code | Glyph | Replacement | Description |
1381 * | -------- | ----- | ----------- | ----------------------------------------- |
1382 * | U+0218 | Ș | S | Latin capital letter S with comma below |
1383 * | U+0219 | È™ | s | Latin small letter s with comma below |
1384 * | U+021A | Èš | T | Latin capital letter T with comma below |
1385 * | U+021B | È› | t | Latin small letter t with comma below |
1386 *
1387 * Vowels with diacritic (Chinese, Hanyu Pinyin):
1388 *
1389 * | Code | Glyph | Replacement | Description |
1390 * | -------- | ----- | ----------- | ----------------------------------------------------- |
1391 * | U+0251 | É‘ | a | Latin small letter alpha |
1392 * | U+1EA0 | Ạ| A | Latin capital letter A with dot below |
1393 * | U+1EA1 | ạ | a | Latin small letter a with dot below |
1394 * | U+1EA2 | Ả | A | Latin capital letter A with hook above |
1395 * | U+1EA3 | ả | a | Latin small letter a with hook above |
1396 * | U+1EA4 | Ấ | A | Latin capital letter A with circumflex and acute |
1397 * | U+1EA5 | ấ | a | Latin small letter a with circumflex and acute |
1398 * | U+1EA6 | Ầ | A | Latin capital letter A with circumflex and grave |
1399 * | U+1EA7 | ầ | a | Latin small letter a with circumflex and grave |
1400 * | U+1EA8 | Ẩ | A | Latin capital letter A with circumflex and hook above |
1401 * | U+1EA9 | ẩ | a | Latin small letter a with circumflex and hook above |
1402 * | U+1EAA | Ẫ | A | Latin capital letter A with circumflex and tilde |
1403 * | U+1EAB | ẫ | a | Latin small letter a with circumflex and tilde |
1404 * | U+1EA6 | Ậ | A | Latin capital letter A with circumflex and dot below |
1405 * | U+1EAD | Ạ| a | Latin small letter a with circumflex and dot below |
1406 * | U+1EAE | Ắ | A | Latin capital letter A with breve and acute |
1407 * | U+1EAF | ắ | a | Latin small letter a with breve and acute |
1408 * | U+1EB0 | Ằ | A | Latin capital letter A with breve and grave |
1409 * | U+1EB1 | ằ | a | Latin small letter a with breve and grave |
1410 * | U+1EB2 | Ẳ | A | Latin capital letter A with breve and hook above |
1411 * | U+1EB3 | ẳ | a | Latin small letter a with breve and hook above |
1412 * | U+1EB4 | Ẵ | A | Latin capital letter A with breve and tilde |
1413 * | U+1EB5 | ẵ | a | Latin small letter a with breve and tilde |
1414 * | U+1EB6 | Ặ | A | Latin capital letter A with breve and dot below |
1415 * | U+1EB7 | ặ | a | Latin small letter a with breve and dot below |
1416 * | U+1EB8 | Ẹ | E | Latin capital letter E with dot below |
1417 * | U+1EB9 | ẹ | e | Latin small letter e with dot below |
1418 * | U+1EBA | Ẻ | E | Latin capital letter E with hook above |
1419 * | U+1EBB | ẻ | e | Latin small letter e with hook above |
1420 * | U+1EBC | Ẽ | E | Latin capital letter E with tilde |
1421 * | U+1EBD | ẽ | e | Latin small letter e with tilde |
1422 * | U+1EBE | Ế | E | Latin capital letter E with circumflex and acute |
1423 * | U+1EBF | ế | e | Latin small letter e with circumflex and acute |
1424 * | U+1EC0 | Ề | E | Latin capital letter E with circumflex and grave |
1425 * | U+1EC1 | á» | e | Latin small letter e with circumflex and grave |
1426 * | U+1EC2 | Ể | E | Latin capital letter E with circumflex and hook above |
1427 * | U+1EC3 | ể | e | Latin small letter e with circumflex and hook above |
1428 * | U+1EC4 | Ễ | E | Latin capital letter E with circumflex and tilde |
1429 * | U+1EC5 | á»… | e | Latin small letter e with circumflex and tilde |
1430 * | U+1EC6 | Ệ | E | Latin capital letter E with circumflex and dot below |
1431 * | U+1EC7 | ệ | e | Latin small letter e with circumflex and dot below |
1432 * | U+1EC8 | Ỉ | I | Latin capital letter I with hook above |
1433 * | U+1EC9 | ỉ | i | Latin small letter i with hook above |
1434 * | U+1ECA | Ị | I | Latin capital letter I with dot below |
1435 * | U+1ECB | ị | i | Latin small letter i with dot below |
1436 * | U+1ECC | Ọ | O | Latin capital letter O with dot below |
1437 * | U+1ECD | á» | o | Latin small letter o with dot below |
1438 * | U+1ECE | Ỏ | O | Latin capital letter O with hook above |
1439 * | U+1ECF | á» | o | Latin small letter o with hook above |
1440 * | U+1ED0 | á» | O | Latin capital letter O with circumflex and acute |
1441 * | U+1ED1 | ố | o | Latin small letter o with circumflex and acute |
1442 * | U+1ED2 | á»’ | O | Latin capital letter O with circumflex and grave |
1443 * | U+1ED3 | ồ | o | Latin small letter o with circumflex and grave |
1444 * | U+1ED4 | á»” | O | Latin capital letter O with circumflex and hook above |
1445 * | U+1ED5 | ổ | o | Latin small letter o with circumflex and hook above |
1446 * | U+1ED6 | á»– | O | Latin capital letter O with circumflex and tilde |
1447 * | U+1ED7 | á»— | o | Latin small letter o with circumflex and tilde |
1448 * | U+1ED8 | Ộ | O | Latin capital letter O with circumflex and dot below |
1449 * | U+1ED9 | á»™ | o | Latin small letter o with circumflex and dot below |
1450 * | U+1EDA | Ớ | O | Latin capital letter O with horn and acute |
1451 * | U+1EDB | á»› | o | Latin small letter o with horn and acute |
1452 * | U+1EDC | Ờ | O | Latin capital letter O with horn and grave |
1453 * | U+1EDD | á» | o | Latin small letter o with horn and grave |
1454 * | U+1EDE | Ở | O | Latin capital letter O with horn and hook above |
1455 * | U+1EDF | ở | o | Latin small letter o with horn and hook above |
1456 * | U+1EE0 | á» | O | Latin capital letter O with horn and tilde |
1457 * | U+1EE1 | ỡ | o | Latin small letter o with horn and tilde |
1458 * | U+1EE2 | Ợ | O | Latin capital letter O with horn and dot below |
1459 * | U+1EE3 | ợ | o | Latin small letter o with horn and dot below |
1460 * | U+1EE4 | Ụ | U | Latin capital letter U with dot below |
1461 * | U+1EE5 | ụ | u | Latin small letter u with dot below |
1462 * | U+1EE6 | Ủ | U | Latin capital letter U with hook above |
1463 * | U+1EE7 | á»§ | u | Latin small letter u with hook above |
1464 * | U+1EE8 | Ứ | U | Latin capital letter U with horn and acute |
1465 * | U+1EE9 | ứ | u | Latin small letter u with horn and acute |
1466 * | U+1EEA | Ừ | U | Latin capital letter U with horn and grave |
1467 * | U+1EEB | ừ | u | Latin small letter u with horn and grave |
1468 * | U+1EEC | Ử | U | Latin capital letter U with horn and hook above |
1469 * | U+1EED | á» | u | Latin small letter u with horn and hook above |
1470 * | U+1EEE | á»® | U | Latin capital letter U with horn and tilde |
1471 * | U+1EEF | ữ | u | Latin small letter u with horn and tilde |
1472 * | U+1EF0 | á»° | U | Latin capital letter U with horn and dot below |
1473 * | U+1EF1 | á»± | u | Latin small letter u with horn and dot below |
1474 * | U+1EF2 | Ỳ | Y | Latin capital letter Y with grave |
1475 * | U+1EF3 | ỳ | y | Latin small letter y with grave |
1476 * | U+1EF4 | á»´ | Y | Latin capital letter Y with dot below |
1477 * | U+1EF5 | ỵ | y | Latin small letter y with dot below |
1478 * | U+1EF6 | á»¶ | Y | Latin capital letter Y with hook above |
1479 * | U+1EF7 | á»· | y | Latin small letter y with hook above |
1480 * | U+1EF8 | Ỹ | Y | Latin capital letter Y with tilde |
1481 * | U+1EF9 | ỹ | y | Latin small letter y with tilde |
1482 *
1483 * German (`de_DE`), German formal (`de_DE_formal`), German (Switzerland) formal (`de_CH`),
1484 * and German (Switzerland) informal (`de_CH_informal`) locales:
1485 *
1486 * | Code | Glyph | Replacement | Description |
1487 * | -------- | ----- | ----------- | --------------------------------------- |
1488 * | U+00C4 | Ä | Ae | Latin capital letter A with diaeresis |
1489 * | U+00E4 | ä | ae | Latin small letter a with diaeresis |
1490 * | U+00D6 | Ö | Oe | Latin capital letter O with diaeresis |
1491 * | U+00F6 | ö | oe | Latin small letter o with diaeresis |
1492 * | U+00DC | Ü | Ue | Latin capital letter U with diaeresis |
1493 * | U+00FC | ü | ue | Latin small letter u with diaeresis |
1494 * | U+00DF | ß | ss | Latin small letter sharp s |
1495 *
1496 * Danish (`da_DK`) locale:
1497 *
1498 * | Code | Glyph | Replacement | Description |
1499 * | -------- | ----- | ----------- | --------------------------------------- |
1500 * | U+00C6 | Æ | Ae | Latin capital letter AE |
1501 * | U+00E6 | æ | ae | Latin small letter ae |
1502 * | U+00D8 | Ø | Oe | Latin capital letter O with stroke |
1503 * | U+00F8 | ø | oe | Latin small letter o with stroke |
1504 * | U+00C5 | Ã… | Aa | Latin capital letter A with ring above |
1505 * | U+00E5 | å | aa | Latin small letter a with ring above |
1506 *
1507 * Catalan (`ca`) locale:
1508 *
1509 * | Code | Glyph | Replacement | Description |
1510 * | -------- | ----- | ----------- | --------------------------------------- |
1511 * | U+00B7 | l·l | ll | Flown dot (between two Ls) |
1512 *
1513 * Serbian (`sr_RS`) and Bosnian (`bs_BA`) locales:
1514 *
1515 * | Code | Glyph | Replacement | Description |
1516 * | -------- | ----- | ----------- | --------------------------------------- |
1517 * | U+0110 | Ä | DJ | Latin capital letter D with stroke |
1518 * | U+0111 | Ä‘ | dj | Latin small letter d with stroke |
1519 *
1520 * @since 1.2.1
1521 * @since 4.6.0 Added locale support for `de_CH`, `de_CH_informal`, and `ca`.
1522 * @since 4.7.0 Added locale support for `sr_RS`.
1523 * @since 4.8.0 Added locale support for `bs_BA`.
1524 *
1525 * @param string $string Text that might have accent characters
1526 * @return string Filtered string with replaced "nice" characters.
1527 */
1528function remove_accents( $string ) {
1529 if ( !preg_match('/[\x80-\xff]/', $string) )
1530 return $string;
1531
1532 if (seems_utf8($string)) {
1533 $chars = array(
1534 // Decompositions for Latin-1 Supplement
1535 'ª' => 'a', 'º' => 'o',
1536 'À' => 'A', 'Ã' => 'A',
1537 'Â' => 'A', 'Ã' => 'A',
1538 'Ä' => 'A', 'Å' => 'A',
1539 'Æ' => 'AE','Ç' => 'C',
1540 'È' => 'E', 'É' => 'E',
1541 'Ê' => 'E', 'Ë' => 'E',
1542 'ÃŒ' => 'I', 'Ã' => 'I',
1543 'ÃŽ' => 'I', 'Ã' => 'I',
1544 'Ã' => 'D', 'Ñ' => 'N',
1545 'Ò' => 'O', 'Ó' => 'O',
1546 'Ô' => 'O', 'Õ' => 'O',
1547 'Ö' => 'O', 'Ù' => 'U',
1548 'Ú' => 'U', 'Û' => 'U',
1549 'Ü' => 'U', 'Ã' => 'Y',
1550 'Þ' => 'TH','ß' => 's',
1551 'à ' => 'a', 'á' => 'a',
1552 'â' => 'a', 'ã' => 'a',
1553 'ä' => 'a', 'å' => 'a',
1554 'æ' => 'ae','ç' => 'c',
1555 'è' => 'e', 'é' => 'e',
1556 'ê' => 'e', 'ë' => 'e',
1557 'ì' => 'i', 'Ã' => 'i',
1558 'î' => 'i', 'ï' => 'i',
1559 'ð' => 'd', 'ñ' => 'n',
1560 'ò' => 'o', 'ó' => 'o',
1561 'ô' => 'o', 'õ' => 'o',
1562 'ö' => 'o', 'ø' => 'o',
1563 'ù' => 'u', 'ú' => 'u',
1564 'û' => 'u', 'ü' => 'u',
1565 'ý' => 'y', 'þ' => 'th',
1566 'ÿ' => 'y', 'Ø' => 'O',
1567 // Decompositions for Latin Extended-A
1568 'Ä€' => 'A', 'Ä' => 'a',
1569 'Ă' => 'A', 'ă' => 'a',
1570 'Ä„' => 'A', 'Ä…' => 'a',
1571 'Ć' => 'C', 'ć' => 'c',
1572 'Ĉ' => 'C', 'ĉ' => 'c',
1573 'ÄŠ' => 'C', 'Ä‹' => 'c',
1574 'ÄŒ' => 'C', 'Ä' => 'c',
1575 'ÄŽ' => 'D', 'Ä' => 'd',
1576 'Ä' => 'D', 'Ä‘' => 'd',
1577 'Ä’' => 'E', 'Ä“' => 'e',
1578 'Ä”' => 'E', 'Ä•' => 'e',
1579 'Ä–' => 'E', 'Ä—' => 'e',
1580 'Ę' => 'E', 'ę' => 'e',
1581 'Äš' => 'E', 'Ä›' => 'e',
1582 'Äœ' => 'G', 'Ä' => 'g',
1583 'Äž' => 'G', 'ÄŸ' => 'g',
1584 'Ä ' => 'G', 'Ä¡' => 'g',
1585 'Ä¢' => 'G', 'Ä£' => 'g',
1586 'Ĥ' => 'H', 'ĥ' => 'h',
1587 'Ħ' => 'H', 'ħ' => 'h',
1588 'Ĩ' => 'I', 'ĩ' => 'i',
1589 'Ī' => 'I', 'ī' => 'i',
1590 'Ĭ' => 'I', 'Ä' => 'i',
1591 'Į' => 'I', 'į' => 'i',
1592 'İ' => 'I', 'ı' => 'i',
1593 'IJ' => 'IJ','ij' => 'ij',
1594 'Ĵ' => 'J', 'ĵ' => 'j',
1595 'Ķ' => 'K', 'ķ' => 'k',
1596 'ĸ' => 'k', 'Ĺ' => 'L',
1597 'ĺ' => 'l', 'Ļ' => 'L',
1598 'ļ' => 'l', 'Ľ' => 'L',
1599 'ľ' => 'l', 'Ŀ' => 'L',
1600 'Å€' => 'l', 'Å' => 'L',
1601 'ł' => 'l', 'Ń' => 'N',
1602 'Å„' => 'n', 'Å…' => 'N',
1603 'ņ' => 'n', 'Ň' => 'N',
1604 'ň' => 'n', 'ʼn' => 'n',
1605 'ÅŠ' => 'N', 'Å‹' => 'n',
1606 'ÅŒ' => 'O', 'Å' => 'o',
1607 'ÅŽ' => 'O', 'Å' => 'o',
1608 'Å' => 'O', 'Å‘' => 'o',
1609 'Å’' => 'OE','Å“' => 'oe',
1610 'Å”' => 'R','Å•' => 'r',
1611 'Å–' => 'R','Å—' => 'r',
1612 'Ř' => 'R','ř' => 'r',
1613 'Åš' => 'S','Å›' => 's',
1614 'Åœ' => 'S','Å' => 's',
1615 'Åž' => 'S','ÅŸ' => 's',
1616 'Å ' => 'S', 'Å¡' => 's',
1617 'Å¢' => 'T', 'Å£' => 't',
1618 'Ť' => 'T', 'ť' => 't',
1619 'Ŧ' => 'T', 'ŧ' => 't',
1620 'Ũ' => 'U', 'ũ' => 'u',
1621 'Ū' => 'U', 'ū' => 'u',
1622 'Ŭ' => 'U', 'Å' => 'u',
1623 'Ů' => 'U', 'ů' => 'u',
1624 'Ű' => 'U', 'ű' => 'u',
1625 'Ų' => 'U', 'ų' => 'u',
1626 'Ŵ' => 'W', 'ŵ' => 'w',
1627 'Ŷ' => 'Y', 'ŷ' => 'y',
1628 'Ÿ' => 'Y', 'Ź' => 'Z',
1629 'ź' => 'z', 'Ż' => 'Z',
1630 'ż' => 'z', 'Ž' => 'Z',
1631 'ž' => 'z', 'ſ' => 's',
1632 // Decompositions for Latin Extended-B
1633 'Ș' => 'S', 'ș' => 's',
1634 'Èš' => 'T', 'È›' => 't',
1635 // Euro Sign
1636 '€' => 'E',
1637 // GBP (Pound) Sign
1638 '£' => '',
1639 // Vowels with diacritic (Vietnamese)
1640 // unmarked
1641 'Æ ' => 'O', 'Æ¡' => 'o',
1642 'Ư' => 'U', 'ư' => 'u',
1643 // grave accent
1644 'Ầ' => 'A', 'ầ' => 'a',
1645 'Ằ' => 'A', 'ằ' => 'a',
1646 'Ề' => 'E', 'á»' => 'e',
1647 'Ồ' => 'O', 'ồ' => 'o',
1648 'Ờ' => 'O', 'á»' => 'o',
1649 'Ừ' => 'U', 'ừ' => 'u',
1650 'Ỳ' => 'Y', 'ỳ' => 'y',
1651 // hook
1652 'Ả' => 'A', 'ả' => 'a',
1653 'Ẩ' => 'A', 'ẩ' => 'a',
1654 'Ẳ' => 'A', 'ẳ' => 'a',
1655 'Ẻ' => 'E', 'ẻ' => 'e',
1656 'Ể' => 'E', 'ể' => 'e',
1657 'Ỉ' => 'I', 'ỉ' => 'i',
1658 'Ỏ' => 'O', 'á»' => 'o',
1659 'Ổ' => 'O', 'ổ' => 'o',
1660 'Ở' => 'O', 'ở' => 'o',
1661 'Ủ' => 'U', 'ủ' => 'u',
1662 'Ử' => 'U', 'á»' => 'u',
1663 'á»¶' => 'Y', 'á»·' => 'y',
1664 // tilde
1665 'Ẫ' => 'A', 'ẫ' => 'a',
1666 'Ẵ' => 'A', 'ẵ' => 'a',
1667 'Ẽ' => 'E', 'ẽ' => 'e',
1668 'Ễ' => 'E', 'ễ' => 'e',
1669 'á»–' => 'O', 'á»—' => 'o',
1670 'Ỡ' => 'O', 'ỡ' => 'o',
1671 'Ữ' => 'U', 'ữ' => 'u',
1672 'Ỹ' => 'Y', 'ỹ' => 'y',
1673 // acute accent
1674 'Ấ' => 'A', 'ấ' => 'a',
1675 'Ắ' => 'A', 'ắ' => 'a',
1676 'Ế' => 'E', 'ế' => 'e',
1677 'á»' => 'O', 'ố' => 'o',
1678 'Ớ' => 'O', 'ớ' => 'o',
1679 'Ứ' => 'U', 'ứ' => 'u',
1680 // dot below
1681 'Ạ' => 'A', 'ạ' => 'a',
1682 'Ậ' => 'A', 'áº' => 'a',
1683 'Ặ' => 'A', 'ặ' => 'a',
1684 'Ẹ' => 'E', 'ẹ' => 'e',
1685 'Ệ' => 'E', 'ệ' => 'e',
1686 'Ị' => 'I', 'ị' => 'i',
1687 'Ọ' => 'O', 'á»' => 'o',
1688 'Ộ' => 'O', 'ộ' => 'o',
1689 'Ợ' => 'O', 'ợ' => 'o',
1690 'Ụ' => 'U', 'ụ' => 'u',
1691 'á»°' => 'U', 'á»±' => 'u',
1692 'Ỵ' => 'Y', 'ỵ' => 'y',
1693 // Vowels with diacritic (Chinese, Hanyu Pinyin)
1694 'É‘' => 'a',
1695 // macron
1696 'Ç•' => 'U', 'Ç–' => 'u',
1697 // acute accent
1698 'Ǘ' => 'U', 'ǘ' => 'u',
1699 // caron
1700 'Ç' => 'A', 'ÇŽ' => 'a',
1701 'Ç' => 'I', 'Ç' => 'i',
1702 'Ç‘' => 'O', 'Ç’' => 'o',
1703 'Ç“' => 'U', 'Ç”' => 'u',
1704 'Ç™' => 'U', 'Çš' => 'u',
1705 // grave accent
1706 'Ǜ' => 'U', 'ǜ' => 'u',
1707 );
1708
1709 // Used for locale-specific rules
1710 $locale = get_locale();
1711
1712 if ( 'de_DE' == $locale || 'de_DE_formal' == $locale || 'de_CH' == $locale || 'de_CH_informal' == $locale ) {
1713 $chars[ 'Ä' ] = 'Ae';
1714 $chars[ 'ä' ] = 'ae';
1715 $chars[ 'Ö' ] = 'Oe';
1716 $chars[ 'ö' ] = 'oe';
1717 $chars[ 'Ü' ] = 'Ue';
1718 $chars[ 'ü' ] = 'ue';
1719 $chars[ 'ß' ] = 'ss';
1720 } elseif ( 'da_DK' === $locale ) {
1721 $chars[ 'Æ' ] = 'Ae';
1722 $chars[ 'æ' ] = 'ae';
1723 $chars[ 'Ø' ] = 'Oe';
1724 $chars[ 'ø' ] = 'oe';
1725 $chars[ 'Ã…' ] = 'Aa';
1726 $chars[ 'Ã¥' ] = 'aa';
1727 } elseif ( 'ca' === $locale ) {
1728 $chars[ 'l·l' ] = 'll';
1729 } elseif ( 'sr_RS' === $locale || 'bs_BA' === $locale ) {
1730 $chars[ 'Ä' ] = 'DJ';
1731 $chars[ 'Ä‘' ] = 'dj';
1732 }
1733
1734 $string = strtr($string, $chars);
1735 } else {
1736 $chars = array();
1737 // Assume ISO-8859-1 if not UTF-8
1738 $chars['in'] = "\x80\x83\x8a\x8e\x9a\x9e"
1739 ."\x9f\xa2\xa5\xb5\xc0\xc1\xc2"
1740 ."\xc3\xc4\xc5\xc7\xc8\xc9\xca"
1741 ."\xcb\xcc\xcd\xce\xcf\xd1\xd2"
1742 ."\xd3\xd4\xd5\xd6\xd8\xd9\xda"
1743 ."\xdb\xdc\xdd\xe0\xe1\xe2\xe3"
1744 ."\xe4\xe5\xe7\xe8\xe9\xea\xeb"
1745 ."\xec\xed\xee\xef\xf1\xf2\xf3"
1746 ."\xf4\xf5\xf6\xf8\xf9\xfa\xfb"
1747 ."\xfc\xfd\xff";
1748
1749 $chars['out'] = "EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy";
1750
1751 $string = strtr($string, $chars['in'], $chars['out']);
1752 $double_chars = array();
1753 $double_chars['in'] = array("\x8c", "\x9c", "\xc6", "\xd0", "\xde", "\xdf", "\xe6", "\xf0", "\xfe");
1754 $double_chars['out'] = array('OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th');
1755 $string = str_replace($double_chars['in'], $double_chars['out'], $string);
1756 }
1757
1758 return $string;
1759}
1760
1761/**
1762 * Sanitizes a filename, replacing whitespace with dashes.
1763 *
1764 * Removes special characters that are illegal in filenames on certain
1765 * operating systems and special characters requiring special escaping
1766 * to manipulate at the command line. Replaces spaces and consecutive
1767 * dashes with a single dash. Trims period, dash and underscore from beginning
1768 * and end of filename. It is not guaranteed that this function will return a
1769 * filename that is allowed to be uploaded.
1770 *
1771 * @since 2.1.0
1772 *
1773 * @param string $filename The filename to be sanitized
1774 * @return string The sanitized filename
1775 */
1776function sanitize_file_name( $filename ) {
1777 $filename_raw = $filename;
1778 $special_chars = array("?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}", "%", "+", chr(0));
1779 /**
1780 * Filters the list of characters to remove from a filename.
1781 *
1782 * @since 2.8.0
1783 *
1784 * @param array $special_chars Characters to remove.
1785 * @param string $filename_raw Filename as it was passed into sanitize_file_name().
1786 */
1787 $special_chars = apply_filters( 'sanitize_file_name_chars', $special_chars, $filename_raw );
1788 $filename = preg_replace( "#\x{00a0}#siu", ' ', $filename );
1789 $filename = str_replace( $special_chars, '', $filename );
1790 $filename = str_replace( array( '%20', '+' ), '-', $filename );
1791 $filename = preg_replace( '/[\r\n\t -]+/', '-', $filename );
1792 $filename = trim( $filename, '.-_' );
1793
1794 if ( false === strpos( $filename, '.' ) ) {
1795 $mime_types = wp_get_mime_types();
1796 $filetype = wp_check_filetype( 'test.' . $filename, $mime_types );
1797 if ( $filetype['ext'] === $filename ) {
1798 $filename = 'unnamed-file.' . $filetype['ext'];
1799 }
1800 }
1801
1802 // Split the filename into a base and extension[s]
1803 $parts = explode('.', $filename);
1804
1805 // Return if only one extension
1806 if ( count( $parts ) <= 2 ) {
1807 /**
1808 * Filters a sanitized filename string.
1809 *
1810 * @since 2.8.0
1811 *
1812 * @param string $filename Sanitized filename.
1813 * @param string $filename_raw The filename prior to sanitization.
1814 */
1815 return apply_filters( 'sanitize_file_name', $filename, $filename_raw );
1816 }
1817
1818 // Process multiple extensions
1819 $filename = array_shift($parts);
1820 $extension = array_pop($parts);
1821 $mimes = get_allowed_mime_types();
1822
1823 /*
1824 * Loop over any intermediate extensions. Postfix them with a trailing underscore
1825 * if they are a 2 - 5 character long alpha string not in the extension whitelist.
1826 */
1827 foreach ( (array) $parts as $part) {
1828 $filename .= '.' . $part;
1829
1830 if ( preg_match("/^[a-zA-Z]{2,5}\d?$/", $part) ) {
1831 $allowed = false;
1832 foreach ( $mimes as $ext_preg => $mime_match ) {
1833 $ext_preg = '!^(' . $ext_preg . ')$!i';
1834 if ( preg_match( $ext_preg, $part ) ) {
1835 $allowed = true;
1836 break;
1837 }
1838 }
1839 if ( !$allowed )
1840 $filename .= '_';
1841 }
1842 }
1843 $filename .= '.' . $extension;
1844 /** This filter is documented in wp-includes/formatting.php */
1845 return apply_filters('sanitize_file_name', $filename, $filename_raw);
1846}
1847
1848/**
1849 * Sanitizes a username, stripping out unsafe characters.
1850 *
1851 * Removes tags, octets, entities, and if strict is enabled, will only keep
1852 * alphanumeric, _, space, ., -, @. After sanitizing, it passes the username,
1853 * raw username (the username in the parameter), and the value of $strict as
1854 * parameters for the {@see 'sanitize_user'} filter.
1855 *
1856 * @since 2.0.0
1857 *
1858 * @param string $username The username to be sanitized.
1859 * @param bool $strict If set limits $username to specific characters. Default false.
1860 * @return string The sanitized username, after passing through filters.
1861 */
1862function sanitize_user( $username, $strict = false ) {
1863 $raw_username = $username;
1864 $username = wp_strip_all_tags( $username );
1865 $username = remove_accents( $username );
1866 // Kill octets
1867 $username = preg_replace( '|%([a-fA-F0-9][a-fA-F0-9])|', '', $username );
1868 $username = preg_replace( '/&.+?;/', '', $username ); // Kill entities
1869
1870 // If strict, reduce to ASCII for max portability.
1871 if ( $strict )
1872 $username = preg_replace( '|[^a-z0-9 _.\-@]|i', '', $username );
1873
1874 $username = trim( $username );
1875 // Consolidate contiguous whitespace
1876 $username = preg_replace( '|\s+|', ' ', $username );
1877
1878 /**
1879 * Filters a sanitized username string.
1880 *
1881 * @since 2.0.1
1882 *
1883 * @param string $username Sanitized username.
1884 * @param string $raw_username The username prior to sanitization.
1885 * @param bool $strict Whether to limit the sanitization to specific characters. Default false.
1886 */
1887 return apply_filters( 'sanitize_user', $username, $raw_username, $strict );
1888}
1889
1890/**
1891 * Sanitizes a string key.
1892 *
1893 * Keys are used as internal identifiers. Lowercase alphanumeric characters, dashes and underscores are allowed.
1894 *
1895 * @since 3.0.0
1896 *
1897 * @param string $key String key
1898 * @return string Sanitized key
1899 */
1900function sanitize_key( $key ) {
1901 $raw_key = $key;
1902 $key = strtolower( $key );
1903 $key = preg_replace( '/[^a-z0-9_\-]/', '', $key );
1904
1905 /**
1906 * Filters a sanitized key string.
1907 *
1908 * @since 3.0.0
1909 *
1910 * @param string $key Sanitized key.
1911 * @param string $raw_key The key prior to sanitization.
1912 */
1913 return apply_filters( 'sanitize_key', $key, $raw_key );
1914}
1915
1916/**
1917 * Sanitizes a title, or returns a fallback title.
1918 *
1919 * Specifically, HTML and PHP tags are stripped. Further actions can be added
1920 * via the plugin API. If $title is empty and $fallback_title is set, the latter
1921 * will be used.
1922 *
1923 * @since 1.0.0
1924 *
1925 * @param string $title The string to be sanitized.
1926 * @param string $fallback_title Optional. A title to use if $title is empty.
1927 * @param string $context Optional. The operation for which the string is sanitized
1928 * @return string The sanitized string.
1929 */
1930function sanitize_title( $title, $fallback_title = '', $context = 'save' ) {
1931 $raw_title = $title;
1932
1933 if ( 'save' == $context )
1934 $title = remove_accents($title);
1935
1936 /**
1937 * Filters a sanitized title string.
1938 *
1939 * @since 1.2.0
1940 *
1941 * @param string $title Sanitized title.
1942 * @param string $raw_title The title prior to sanitization.
1943 * @param string $context The context for which the title is being sanitized.
1944 */
1945 $title = apply_filters( 'sanitize_title', $title, $raw_title, $context );
1946
1947 if ( '' === $title || false === $title )
1948 $title = $fallback_title;
1949
1950 return $title;
1951}
1952
1953/**
1954 * Sanitizes a title with the 'query' context.
1955 *
1956 * Used for querying the database for a value from URL.
1957 *
1958 * @since 3.1.0
1959 *
1960 * @param string $title The string to be sanitized.
1961 * @return string The sanitized string.
1962 */
1963function sanitize_title_for_query( $title ) {
1964 return sanitize_title( $title, '', 'query' );
1965}
1966
1967/**
1968 * Sanitizes a title, replacing whitespace and a few other characters with dashes.
1969 *
1970 * Limits the output to alphanumeric characters, underscore (_) and dash (-).
1971 * Whitespace becomes a dash.
1972 *
1973 * @since 1.2.0
1974 *
1975 * @param string $title The title to be sanitized.
1976 * @param string $raw_title Optional. Not used.
1977 * @param string $context Optional. The operation for which the string is sanitized.
1978 * @return string The sanitized title.
1979 */
1980function sanitize_title_with_dashes( $title, $raw_title = '', $context = 'display' ) {
1981 $title = strip_tags($title);
1982 // Preserve escaped octets.
1983 $title = preg_replace('|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title);
1984 // Remove percent signs that are not part of an octet.
1985 $title = str_replace('%', '', $title);
1986 // Restore octets.
1987 $title = preg_replace('|---([a-fA-F0-9][a-fA-F0-9])---|', '%$1', $title);
1988
1989 if (seems_utf8($title)) {
1990 if (function_exists('mb_strtolower')) {
1991 $title = mb_strtolower($title, 'UTF-8');
1992 }
1993 $title = utf8_uri_encode($title, 200);
1994 }
1995
1996 $title = strtolower($title);
1997
1998 if ( 'save' == $context ) {
1999 // Convert nbsp, ndash and mdash to hyphens
2000 $title = str_replace( array( '%c2%a0', '%e2%80%93', '%e2%80%94' ), '-', $title );
2001 // Convert nbsp, ndash and mdash HTML entities to hyphens
2002 $title = str_replace( array( ' ', ' ', '–', '–', '—', '—' ), '-', $title );
2003 // Convert forward slash to hyphen
2004 $title = str_replace( '/', '-', $title );
2005
2006 // Strip these characters entirely
2007 $title = str_replace( array(
2008 // iexcl and iquest
2009 '%c2%a1', '%c2%bf',
2010 // angle quotes
2011 '%c2%ab', '%c2%bb', '%e2%80%b9', '%e2%80%ba',
2012 // curly quotes
2013 '%e2%80%98', '%e2%80%99', '%e2%80%9c', '%e2%80%9d',
2014 '%e2%80%9a', '%e2%80%9b', '%e2%80%9e', '%e2%80%9f',
2015 // copy, reg, deg, hellip and trade
2016 '%c2%a9', '%c2%ae', '%c2%b0', '%e2%80%a6', '%e2%84%a2',
2017 // acute accents
2018 '%c2%b4', '%cb%8a', '%cc%81', '%cd%81',
2019 // grave accent, macron, caron
2020 '%cc%80', '%cc%84', '%cc%8c',
2021 ), '', $title );
2022
2023 // Convert times to x
2024 $title = str_replace( '%c3%97', 'x', $title );
2025 }
2026
2027 $title = preg_replace('/&.+?;/', '', $title); // kill entities
2028 $title = str_replace('.', '-', $title);
2029
2030 $title = preg_replace('/[^%a-z0-9 _-]/', '', $title);
2031 $title = preg_replace('/\s+/', '-', $title);
2032 $title = preg_replace('|-+|', '-', $title);
2033 $title = trim($title, '-');
2034
2035 return $title;
2036}
2037
2038/**
2039 * Ensures a string is a valid SQL 'order by' clause.
2040 *
2041 * Accepts one or more columns, with or without a sort order (ASC / DESC).
2042 * e.g. 'column_1', 'column_1, column_2', 'column_1 ASC, column_2 DESC' etc.
2043 *
2044 * Also accepts 'RAND()'.
2045 *
2046 * @since 2.5.1
2047 *
2048 * @param string $orderby Order by clause to be validated.
2049 * @return string|false Returns $orderby if valid, false otherwise.
2050 */
2051function sanitize_sql_orderby( $orderby ) {
2052 if ( preg_match( '/^\s*(([a-z0-9_]+|`[a-z0-9_]+`)(\s+(ASC|DESC))?\s*(,\s*(?=[a-z0-9_`])|$))+$/i', $orderby ) || preg_match( '/^\s*RAND\(\s*\)\s*$/i', $orderby ) ) {
2053 return $orderby;
2054 }
2055 return false;
2056}
2057
2058/**
2059 * Sanitizes an HTML classname to ensure it only contains valid characters.
2060 *
2061 * Strips the string down to A-Z,a-z,0-9,_,-. If this results in an empty
2062 * string then it will return the alternative value supplied.
2063 *
2064 * @todo Expand to support the full range of CDATA that a class attribute can contain.
2065 *
2066 * @since 2.8.0
2067 *
2068 * @param string $class The classname to be sanitized
2069 * @param string $fallback Optional. The value to return if the sanitization ends up as an empty string.
2070 * Defaults to an empty string.
2071 * @return string The sanitized value
2072 */
2073function sanitize_html_class( $class, $fallback = '' ) {
2074 //Strip out any % encoded octets
2075 $sanitized = preg_replace( '|%[a-fA-F0-9][a-fA-F0-9]|', '', $class );
2076
2077 //Limit to A-Z,a-z,0-9,_,-
2078 $sanitized = preg_replace( '/[^A-Za-z0-9_-]/', '', $sanitized );
2079
2080 if ( '' == $sanitized && $fallback ) {
2081 return sanitize_html_class( $fallback );
2082 }
2083 /**
2084 * Filters a sanitized HTML class string.
2085 *
2086 * @since 2.8.0
2087 *
2088 * @param string $sanitized The sanitized HTML class.
2089 * @param string $class HTML class before sanitization.
2090 * @param string $fallback The fallback string.
2091 */
2092 return apply_filters( 'sanitize_html_class', $sanitized, $class, $fallback );
2093}
2094
2095/**
2096 * Converts lone & characters into `&` (a.k.a. `&`)
2097 *
2098 * @since 0.71
2099 *
2100 * @param string $content String of characters to be converted.
2101 * @param string $deprecated Not used.
2102 * @return string Converted string.
2103 */
2104function convert_chars( $content, $deprecated = '' ) {
2105 if ( ! empty( $deprecated ) ) {
2106 _deprecated_argument( __FUNCTION__, '0.71' );
2107 }
2108
2109 if ( strpos( $content, '&' ) !== false ) {
2110 $content = preg_replace( '/&([^#])(?![a-z1-4]{1,8};)/i', '&$1', $content );
2111 }
2112
2113 return $content;
2114}
2115
2116/**
2117 * Converts invalid Unicode references range to valid range.
2118 *
2119 * @since 4.3.0
2120 *
2121 * @param string $content String with entities that need converting.
2122 * @return string Converted string.
2123 */
2124function convert_invalid_entities( $content ) {
2125 $wp_htmltranswinuni = array(
2126 '€' => '€', // the Euro sign
2127 '' => '',
2128 '‚' => '‚', // these are Windows CP1252 specific characters
2129 'ƒ' => 'ƒ', // they would look weird on non-Windows browsers
2130 '„' => '„',
2131 '…' => '…',
2132 '†' => '†',
2133 '‡' => '‡',
2134 'ˆ' => 'ˆ',
2135 '‰' => '‰',
2136 'Š' => 'Š',
2137 '‹' => '‹',
2138 'Œ' => 'Œ',
2139 '' => '',
2140 'Ž' => 'Ž',
2141 '' => '',
2142 '' => '',
2143 '‘' => '‘',
2144 '’' => '’',
2145 '“' => '“',
2146 '”' => '”',
2147 '•' => '•',
2148 '–' => '–',
2149 '—' => '—',
2150 '˜' => '˜',
2151 '™' => '™',
2152 'š' => 'š',
2153 '›' => '›',
2154 'œ' => 'œ',
2155 '' => '',
2156 'ž' => 'ž',
2157 'Ÿ' => 'Ÿ'
2158 );
2159
2160 if ( strpos( $content, '' ) !== false ) {
2161 $content = strtr( $content, $wp_htmltranswinuni );
2162 }
2163
2164 return $content;
2165}
2166
2167/**
2168 * Balances tags if forced to, or if the 'use_balanceTags' option is set to true.
2169 *
2170 * @since 0.71
2171 *
2172 * @param string $text Text to be balanced
2173 * @param bool $force If true, forces balancing, ignoring the value of the option. Default false.
2174 * @return string Balanced text
2175 */
2176function balanceTags( $text, $force = false ) {
2177 if ( $force || get_option('use_balanceTags') == 1 ) {
2178 return force_balance_tags( $text );
2179 } else {
2180 return $text;
2181 }
2182}
2183
2184/**
2185 * Balances tags of string using a modified stack.
2186 *
2187 * @since 2.0.4
2188 *
2189 * @author Leonard Lin <leonard@acm.org>
2190 * @license GPL
2191 * @copyright November 4, 2001
2192 * @version 1.1
2193 * @todo Make better - change loop condition to $text in 1.2
2194 * @internal Modified by Scott Reilly (coffee2code) 02 Aug 2004
2195 * 1.1 Fixed handling of append/stack pop order of end text
2196 * Added Cleaning Hooks
2197 * 1.0 First Version
2198 *
2199 * @param string $text Text to be balanced.
2200 * @return string Balanced text.
2201 */
2202function force_balance_tags( $text ) {
2203 $tagstack = array();
2204 $stacksize = 0;
2205 $tagqueue = '';
2206 $newtext = '';
2207 // Known single-entity/self-closing tags
2208 $single_tags = array( 'area', 'base', 'basefont', 'br', 'col', 'command', 'embed', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param', 'source' );
2209 // Tags that can be immediately nested within themselves
2210 $nestable_tags = array( 'blockquote', 'div', 'object', 'q', 'span' );
2211
2212 // WP bug fix for comments - in case you REALLY meant to type '< !--'
2213 $text = str_replace('< !--', '< !--', $text);
2214 // WP bug fix for LOVE <3 (and other situations with '<' before a number)
2215 $text = preg_replace('#<([0-9]{1})#', '<$1', $text);
2216
2217 while ( preg_match("/<(\/?[\w:]*)\s*([^>]*)>/", $text, $regex) ) {
2218 $newtext .= $tagqueue;
2219
2220 $i = strpos($text, $regex[0]);
2221 $l = strlen($regex[0]);
2222
2223 // clear the shifter
2224 $tagqueue = '';
2225 // Pop or Push
2226 if ( isset($regex[1][0]) && '/' == $regex[1][0] ) { // End Tag
2227 $tag = strtolower(substr($regex[1],1));
2228 // if too many closing tags
2229 if ( $stacksize <= 0 ) {
2230 $tag = '';
2231 // or close to be safe $tag = '/' . $tag;
2232 }
2233 // if stacktop value = tag close value then pop
2234 elseif ( $tagstack[$stacksize - 1] == $tag ) { // found closing tag
2235 $tag = '</' . $tag . '>'; // Close Tag
2236 // Pop
2237 array_pop( $tagstack );
2238 $stacksize--;
2239 } else { // closing tag not at top, search for it
2240 for ( $j = $stacksize-1; $j >= 0; $j-- ) {
2241 if ( $tagstack[$j] == $tag ) {
2242 // add tag to tagqueue
2243 for ( $k = $stacksize-1; $k >= $j; $k--) {
2244 $tagqueue .= '</' . array_pop( $tagstack ) . '>';
2245 $stacksize--;
2246 }
2247 break;
2248 }
2249 }
2250 $tag = '';
2251 }
2252 } else { // Begin Tag
2253 $tag = strtolower($regex[1]);
2254
2255 // Tag Cleaning
2256
2257 // If it's an empty tag "< >", do nothing
2258 if ( '' == $tag ) {
2259 // do nothing
2260 }
2261 // ElseIf it presents itself as a self-closing tag...
2262 elseif ( substr( $regex[2], -1 ) == '/' ) {
2263 // ...but it isn't a known single-entity self-closing tag, then don't let it be treated as such and
2264 // immediately close it with a closing tag (the tag will encapsulate no text as a result)
2265 if ( ! in_array( $tag, $single_tags ) )
2266 $regex[2] = trim( substr( $regex[2], 0, -1 ) ) . "></$tag";
2267 }
2268 // ElseIf it's a known single-entity tag but it doesn't close itself, do so
2269 elseif ( in_array($tag, $single_tags) ) {
2270 $regex[2] .= '/';
2271 }
2272 // Else it's not a single-entity tag
2273 else {
2274 // If the top of the stack is the same as the tag we want to push, close previous tag
2275 if ( $stacksize > 0 && !in_array($tag, $nestable_tags) && $tagstack[$stacksize - 1] == $tag ) {
2276 $tagqueue = '</' . array_pop( $tagstack ) . '>';
2277 $stacksize--;
2278 }
2279 $stacksize = array_push( $tagstack, $tag );
2280 }
2281
2282 // Attributes
2283 $attributes = $regex[2];
2284 if ( ! empty( $attributes ) && $attributes[0] != '>' )
2285 $attributes = ' ' . $attributes;
2286
2287 $tag = '<' . $tag . $attributes . '>';
2288 //If already queuing a close tag, then put this tag on, too
2289 if ( !empty($tagqueue) ) {
2290 $tagqueue .= $tag;
2291 $tag = '';
2292 }
2293 }
2294 $newtext .= substr($text, 0, $i) . $tag;
2295 $text = substr($text, $i + $l);
2296 }
2297
2298 // Clear Tag Queue
2299 $newtext .= $tagqueue;
2300
2301 // Add Remaining text
2302 $newtext .= $text;
2303
2304 // Empty Stack
2305 while( $x = array_pop($tagstack) )
2306 $newtext .= '</' . $x . '>'; // Add remaining tags to close
2307
2308 // WP fix for the bug with HTML comments
2309 $newtext = str_replace("< !--","<!--",$newtext);
2310 $newtext = str_replace("< !--","< !--",$newtext);
2311
2312 return $newtext;
2313}
2314
2315/**
2316 * Acts on text which is about to be edited.
2317 *
2318 * The $content is run through esc_textarea(), which uses htmlspecialchars()
2319 * to convert special characters to HTML entities. If `$richedit` is set to true,
2320 * it is simply a holder for the {@see 'format_to_edit'} filter.
2321 *
2322 * @since 0.71
2323 * @since 4.4.0 The `$richedit` parameter was renamed to `$rich_text` for clarity.
2324 *
2325 * @param string $content The text about to be edited.
2326 * @param bool $rich_text Optional. Whether `$content` should be considered rich text,
2327 * in which case it would not be passed through esc_textarea().
2328 * Default false.
2329 * @return string The text after the filter (and possibly htmlspecialchars()) has been run.
2330 */
2331function format_to_edit( $content, $rich_text = false ) {
2332 /**
2333 * Filters the text to be formatted for editing.
2334 *
2335 * @since 1.2.0
2336 *
2337 * @param string $content The text, prior to formatting for editing.
2338 */
2339 $content = apply_filters( 'format_to_edit', $content );
2340 if ( ! $rich_text )
2341 $content = esc_textarea( $content );
2342 return $content;
2343}
2344
2345/**
2346 * Add leading zeros when necessary.
2347 *
2348 * If you set the threshold to '4' and the number is '10', then you will get
2349 * back '0010'. If you set the threshold to '4' and the number is '5000', then you
2350 * will get back '5000'.
2351 *
2352 * Uses sprintf to append the amount of zeros based on the $threshold parameter
2353 * and the size of the number. If the number is large enough, then no zeros will
2354 * be appended.
2355 *
2356 * @since 0.71
2357 *
2358 * @param int $number Number to append zeros to if not greater than threshold.
2359 * @param int $threshold Digit places number needs to be to not have zeros added.
2360 * @return string Adds leading zeros to number if needed.
2361 */
2362function zeroise( $number, $threshold ) {
2363 return sprintf( '%0' . $threshold . 's', $number );
2364}
2365
2366/**
2367 * Adds backslashes before letters and before a number at the start of a string.
2368 *
2369 * @since 0.71
2370 *
2371 * @param string $string Value to which backslashes will be added.
2372 * @return string String with backslashes inserted.
2373 */
2374function backslashit( $string ) {
2375 if ( isset( $string[0] ) && $string[0] >= '0' && $string[0] <= '9' )
2376 $string = '\\\\' . $string;
2377 return addcslashes( $string, 'A..Za..z' );
2378}
2379
2380/**
2381 * Appends a trailing slash.
2382 *
2383 * Will remove trailing forward and backslashes if it exists already before adding
2384 * a trailing forward slash. This prevents double slashing a string or path.
2385 *
2386 * The primary use of this is for paths and thus should be used for paths. It is
2387 * not restricted to paths and offers no specific path support.
2388 *
2389 * @since 1.2.0
2390 *
2391 * @param string $string What to add the trailing slash to.
2392 * @return string String with trailing slash added.
2393 */
2394function trailingslashit( $string ) {
2395 return untrailingslashit( $string ) . '/';
2396}
2397
2398/**
2399 * Removes trailing forward slashes and backslashes if they exist.
2400 *
2401 * The primary use of this is for paths and thus should be used for paths. It is
2402 * not restricted to paths and offers no specific path support.
2403 *
2404 * @since 2.2.0
2405 *
2406 * @param string $string What to remove the trailing slashes from.
2407 * @return string String without the trailing slashes.
2408 */
2409function untrailingslashit( $string ) {
2410 return rtrim( $string, '/\\' );
2411}
2412
2413/**
2414 * Adds slashes to escape strings.
2415 *
2416 * Slashes will first be removed if magic_quotes_gpc is set, see {@link
2417 * https://secure.php.net/magic_quotes} for more details.
2418 *
2419 * @since 0.71
2420 *
2421 * @param string $gpc The string returned from HTTP request data.
2422 * @return string Returns a string escaped with slashes.
2423 */
2424function addslashes_gpc($gpc) {
2425 if ( get_magic_quotes_gpc() )
2426 $gpc = stripslashes($gpc);
2427
2428 return wp_slash($gpc);
2429}
2430
2431/**
2432 * Navigates through an array, object, or scalar, and removes slashes from the values.
2433 *
2434 * @since 2.0.0
2435 *
2436 * @param mixed $value The value to be stripped.
2437 * @return mixed Stripped value.
2438 */
2439function stripslashes_deep( $value ) {
2440 return map_deep( $value, 'stripslashes_from_strings_only' );
2441}
2442
2443/**
2444 * Callback function for `stripslashes_deep()` which strips slashes from strings.
2445 *
2446 * @since 4.4.0
2447 *
2448 * @param mixed $value The array or string to be stripped.
2449 * @return mixed $value The stripped value.
2450 */
2451function stripslashes_from_strings_only( $value ) {
2452 return is_string( $value ) ? stripslashes( $value ) : $value;
2453}
2454
2455/**
2456 * Navigates through an array, object, or scalar, and encodes the values to be used in a URL.
2457 *
2458 * @since 2.2.0
2459 *
2460 * @param mixed $value The array or string to be encoded.
2461 * @return mixed $value The encoded value.
2462 */
2463function urlencode_deep( $value ) {
2464 return map_deep( $value, 'urlencode' );
2465}
2466
2467/**
2468 * Navigates through an array, object, or scalar, and raw-encodes the values to be used in a URL.
2469 *
2470 * @since 3.4.0
2471 *
2472 * @param mixed $value The array or string to be encoded.
2473 * @return mixed $value The encoded value.
2474 */
2475function rawurlencode_deep( $value ) {
2476 return map_deep( $value, 'rawurlencode' );
2477}
2478
2479/**
2480 * Navigates through an array, object, or scalar, and decodes URL-encoded values
2481 *
2482 * @since 4.4.0
2483 *
2484 * @param mixed $value The array or string to be decoded.
2485 * @return mixed $value The decoded value.
2486 */
2487function urldecode_deep( $value ) {
2488 return map_deep( $value, 'urldecode' );
2489}
2490
2491/**
2492 * Converts email addresses characters to HTML entities to block spam bots.
2493 *
2494 * @since 0.71
2495 *
2496 * @param string $email_address Email address.
2497 * @param int $hex_encoding Optional. Set to 1 to enable hex encoding.
2498 * @return string Converted email address.
2499 */
2500function antispambot( $email_address, $hex_encoding = 0 ) {
2501 $email_no_spam_address = '';
2502 for ( $i = 0, $len = strlen( $email_address ); $i < $len; $i++ ) {
2503 $j = rand( 0, 1 + $hex_encoding );
2504 if ( $j == 0 ) {
2505 $email_no_spam_address .= '&#' . ord( $email_address[$i] ) . ';';
2506 } elseif ( $j == 1 ) {
2507 $email_no_spam_address .= $email_address[$i];
2508 } elseif ( $j == 2 ) {
2509 $email_no_spam_address .= '%' . zeroise( dechex( ord( $email_address[$i] ) ), 2 );
2510 }
2511 }
2512
2513 return str_replace( '@', '@', $email_no_spam_address );
2514}
2515
2516/**
2517 * Callback to convert URI match to HTML A element.
2518 *
2519 * This function was backported from 2.5.0 to 2.3.2. Regex callback for make_clickable().
2520 *
2521 * @since 2.3.2
2522 * @access private
2523 *
2524 * @param array $matches Single Regex Match.
2525 * @return string HTML A element with URI address.
2526 */
2527function _make_url_clickable_cb( $matches ) {
2528 $url = $matches[2];
2529
2530 if ( ')' == $matches[3] && strpos( $url, '(' ) ) {
2531 // If the trailing character is a closing parethesis, and the URL has an opening parenthesis in it, add the closing parenthesis to the URL.
2532 // Then we can let the parenthesis balancer do its thing below.
2533 $url .= $matches[3];
2534 $suffix = '';
2535 } else {
2536 $suffix = $matches[3];
2537 }
2538
2539 // Include parentheses in the URL only if paired
2540 while ( substr_count( $url, '(' ) < substr_count( $url, ')' ) ) {
2541 $suffix = strrchr( $url, ')' ) . $suffix;
2542 $url = substr( $url, 0, strrpos( $url, ')' ) );
2543 }
2544
2545 $url = esc_url($url);
2546 if ( empty($url) )
2547 return $matches[0];
2548
2549 return $matches[1] . "<a href=\"$url\" rel=\"nofollow\">$url</a>" . $suffix;
2550}
2551
2552/**
2553 * Callback to convert URL match to HTML A element.
2554 *
2555 * This function was backported from 2.5.0 to 2.3.2. Regex callback for make_clickable().
2556 *
2557 * @since 2.3.2
2558 * @access private
2559 *
2560 * @param array $matches Single Regex Match.
2561 * @return string HTML A element with URL address.
2562 */
2563function _make_web_ftp_clickable_cb( $matches ) {
2564 $ret = '';
2565 $dest = $matches[2];
2566 $dest = 'http://' . $dest;
2567
2568 // removed trailing [.,;:)] from URL
2569 if ( in_array( substr($dest, -1), array('.', ',', ';', ':', ')') ) === true ) {
2570 $ret = substr($dest, -1);
2571 $dest = substr($dest, 0, strlen($dest)-1);
2572 }
2573
2574 $dest = esc_url($dest);
2575 if ( empty($dest) )
2576 return $matches[0];
2577
2578 return $matches[1] . "<a href=\"$dest\" rel=\"nofollow\">$dest</a>$ret";
2579}
2580
2581/**
2582 * Callback to convert email address match to HTML A element.
2583 *
2584 * This function was backported from 2.5.0 to 2.3.2. Regex callback for make_clickable().
2585 *
2586 * @since 2.3.2
2587 * @access private
2588 *
2589 * @param array $matches Single Regex Match.
2590 * @return string HTML A element with email address.
2591 */
2592function _make_email_clickable_cb( $matches ) {
2593 $email = $matches[2] . '@' . $matches[3];
2594 return $matches[1] . "<a href=\"mailto:$email\">$email</a>";
2595}
2596
2597/**
2598 * Convert plaintext URI to HTML links.
2599 *
2600 * Converts URI, www and ftp, and email addresses. Finishes by fixing links
2601 * within links.
2602 *
2603 * @since 0.71
2604 *
2605 * @param string $text Content to convert URIs.
2606 * @return string Content with converted URIs.
2607 */
2608function make_clickable( $text ) {
2609 $r = '';
2610 $textarr = preg_split( '/(<[^<>]+>)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); // split out HTML tags
2611 $nested_code_pre = 0; // Keep track of how many levels link is nested inside <pre> or <code>
2612 foreach ( $textarr as $piece ) {
2613
2614 if ( preg_match( '|^<code[\s>]|i', $piece ) || preg_match( '|^<pre[\s>]|i', $piece ) || preg_match( '|^<script[\s>]|i', $piece ) || preg_match( '|^<style[\s>]|i', $piece ) )
2615 $nested_code_pre++;
2616 elseif ( $nested_code_pre && ( '</code>' === strtolower( $piece ) || '</pre>' === strtolower( $piece ) || '</script>' === strtolower( $piece ) || '</style>' === strtolower( $piece ) ) )
2617 $nested_code_pre--;
2618
2619 if ( $nested_code_pre || empty( $piece ) || ( $piece[0] === '<' && ! preg_match( '|^<\s*[\w]{1,20}+://|', $piece ) ) ) {
2620 $r .= $piece;
2621 continue;
2622 }
2623
2624 // Long strings might contain expensive edge cases ...
2625 if ( 10000 < strlen( $piece ) ) {
2626 // ... break it up
2627 foreach ( _split_str_by_whitespace( $piece, 2100 ) as $chunk ) { // 2100: Extra room for scheme and leading and trailing paretheses
2628 if ( 2101 < strlen( $chunk ) ) {
2629 $r .= $chunk; // Too big, no whitespace: bail.
2630 } else {
2631 $r .= make_clickable( $chunk );
2632 }
2633 }
2634 } else {
2635 $ret = " $piece "; // Pad with whitespace to simplify the regexes
2636
2637 $url_clickable = '~
2638 ([\\s(<.,;:!?]) # 1: Leading whitespace, or punctuation
2639 ( # 2: URL
2640 [\\w]{1,20}+:// # Scheme and hier-part prefix
2641 (?=\S{1,2000}\s) # Limit to URLs less than about 2000 characters long
2642 [\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]*+ # Non-punctuation URL character
2643 (?: # Unroll the Loop: Only allow puctuation URL character if followed by a non-punctuation URL character
2644 [\'.,;:!?)] # Punctuation URL character
2645 [\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]++ # Non-punctuation URL character
2646 )*
2647 )
2648 (\)?) # 3: Trailing closing parenthesis (for parethesis balancing post processing)
2649 ~xS'; // The regex is a non-anchored pattern and does not have a single fixed starting character.
2650 // Tell PCRE to spend more time optimizing since, when used on a page load, it will probably be used several times.
2651
2652 $ret = preg_replace_callback( $url_clickable, '_make_url_clickable_cb', $ret );
2653
2654 $ret = preg_replace_callback( '#([\s>])((www|ftp)\.[\w\\x80-\\xff\#$%&~/.\-;:=,?@\[\]+]+)#is', '_make_web_ftp_clickable_cb', $ret );
2655 $ret = preg_replace_callback( '#([\s>])([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})#i', '_make_email_clickable_cb', $ret );
2656
2657 $ret = substr( $ret, 1, -1 ); // Remove our whitespace padding.
2658 $r .= $ret;
2659 }
2660 }
2661
2662 // Cleanup of accidental links within links
2663 return preg_replace( '#(<a([ \r\n\t]+[^>]+?>|>))<a [^>]+?>([^>]+?)</a></a>#i', "$1$3</a>", $r );
2664}
2665
2666/**
2667 * Breaks a string into chunks by splitting at whitespace characters.
2668 * The length of each returned chunk is as close to the specified length goal as possible,
2669 * with the caveat that each chunk includes its trailing delimiter.
2670 * Chunks longer than the goal are guaranteed to not have any inner whitespace.
2671 *
2672 * Joining the returned chunks with empty delimiters reconstructs the input string losslessly.
2673 *
2674 * Input string must have no null characters (or eventual transformations on output chunks must not care about null characters)
2675 *
2676 * _split_str_by_whitespace( "1234 67890 1234 67890a cd 1234 890 123456789 1234567890a 45678 1 3 5 7 90 ", 10 ) ==
2677 * array (
2678 * 0 => '1234 67890 ', // 11 characters: Perfect split
2679 * 1 => '1234 ', // 5 characters: '1234 67890a' was too long
2680 * 2 => '67890a cd ', // 10 characters: '67890a cd 1234' was too long
2681 * 3 => '1234 890 ', // 11 characters: Perfect split
2682 * 4 => '123456789 ', // 10 characters: '123456789 1234567890a' was too long
2683 * 5 => '1234567890a ', // 12 characters: Too long, but no inner whitespace on which to split
2684 * 6 => ' 45678 ', // 11 characters: Perfect split
2685 * 7 => '1 3 5 7 90 ', // 11 characters: End of $string
2686 * );
2687 *
2688 * @since 3.4.0
2689 * @access private
2690 *
2691 * @param string $string The string to split.
2692 * @param int $goal The desired chunk length.
2693 * @return array Numeric array of chunks.
2694 */
2695function _split_str_by_whitespace( $string, $goal ) {
2696 $chunks = array();
2697
2698 $string_nullspace = strtr( $string, "\r\n\t\v\f ", "\000\000\000\000\000\000" );
2699
2700 while ( $goal < strlen( $string_nullspace ) ) {
2701 $pos = strrpos( substr( $string_nullspace, 0, $goal + 1 ), "\000" );
2702
2703 if ( false === $pos ) {
2704 $pos = strpos( $string_nullspace, "\000", $goal + 1 );
2705 if ( false === $pos ) {
2706 break;
2707 }
2708 }
2709
2710 $chunks[] = substr( $string, 0, $pos + 1 );
2711 $string = substr( $string, $pos + 1 );
2712 $string_nullspace = substr( $string_nullspace, $pos + 1 );
2713 }
2714
2715 if ( $string ) {
2716 $chunks[] = $string;
2717 }
2718
2719 return $chunks;
2720}
2721
2722/**
2723 * Adds rel nofollow string to all HTML A elements in content.
2724 *
2725 * @since 1.5.0
2726 *
2727 * @param string $text Content that may contain HTML A elements.
2728 * @return string Converted content.
2729 */
2730function wp_rel_nofollow( $text ) {
2731 // This is a pre save filter, so text is already escaped.
2732 $text = stripslashes($text);
2733 $text = preg_replace_callback('|<a (.+?)>|i', 'wp_rel_nofollow_callback', $text);
2734 return wp_slash( $text );
2735}
2736
2737/**
2738 * Callback to add rel=nofollow string to HTML A element.
2739 *
2740 * Will remove already existing rel="nofollow" and rel='nofollow' from the
2741 * string to prevent from invalidating (X)HTML.
2742 *
2743 * @since 2.3.0
2744 *
2745 * @param array $matches Single Match
2746 * @return string HTML A Element with rel nofollow.
2747 */
2748function wp_rel_nofollow_callback( $matches ) {
2749 $text = $matches[1];
2750 $atts = shortcode_parse_atts( $matches[1] );
2751 $rel = 'nofollow';
2752
2753 if ( preg_match( '%href=["\'](' . preg_quote( set_url_scheme( home_url(), 'http' ) ) . ')%i', $text ) ||
2754 preg_match( '%href=["\'](' . preg_quote( set_url_scheme( home_url(), 'https' ) ) . ')%i', $text )
2755 ) {
2756 return "<a $text>";
2757 }
2758
2759 if ( ! empty( $atts['rel'] ) ) {
2760 $parts = array_map( 'trim', explode( ' ', $atts['rel'] ) );
2761 if ( false === array_search( 'nofollow', $parts ) ) {
2762 $parts[] = 'nofollow';
2763 }
2764 $rel = implode( ' ', $parts );
2765 unset( $atts['rel'] );
2766
2767 $html = '';
2768 foreach ( $atts as $name => $value ) {
2769 $html .= "{$name}=\"$value\" ";
2770 }
2771 $text = trim( $html );
2772 }
2773 return "<a $text rel=\"$rel\">";
2774}
2775
2776/**
2777 * Convert one smiley code to the icon graphic file equivalent.
2778 *
2779 * Callback handler for convert_smilies().
2780 *
2781 * Looks up one smiley code in the $wpsmiliestrans global array and returns an
2782 * `<img>` string for that smiley.
2783 *
2784 * @since 2.8.0
2785 *
2786 * @global array $wpsmiliestrans
2787 *
2788 * @param array $matches Single match. Smiley code to convert to image.
2789 * @return string Image string for smiley.
2790 */
2791function translate_smiley( $matches ) {
2792 global $wpsmiliestrans;
2793
2794 if ( count( $matches ) == 0 )
2795 return '';
2796
2797 $smiley = trim( reset( $matches ) );
2798 $img = $wpsmiliestrans[ $smiley ];
2799
2800 $matches = array();
2801 $ext = preg_match( '/\.([^.]+)$/', $img, $matches ) ? strtolower( $matches[1] ) : false;
2802 $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png' );
2803
2804 // Don't convert smilies that aren't images - they're probably emoji.
2805 if ( ! in_array( $ext, $image_exts ) ) {
2806 return $img;
2807 }
2808
2809 /**
2810 * Filters the Smiley image URL before it's used in the image element.
2811 *
2812 * @since 2.9.0
2813 *
2814 * @param string $smiley_url URL for the smiley image.
2815 * @param string $img Filename for the smiley image.
2816 * @param string $site_url Site URL, as returned by site_url().
2817 */
2818 $src_url = apply_filters( 'smilies_src', includes_url( "images/smilies/$img" ), $img, site_url() );
2819
2820 return sprintf( '<img src="%s" alt="%s" class="wp-smiley" style="height: 1em; max-height: 1em;" />', esc_url( $src_url ), esc_attr( $smiley ) );
2821}
2822
2823/**
2824 * Convert text equivalent of smilies to images.
2825 *
2826 * Will only convert smilies if the option 'use_smilies' is true and the global
2827 * used in the function isn't empty.
2828 *
2829 * @since 0.71
2830 *
2831 * @global string|array $wp_smiliessearch
2832 *
2833 * @param string $text Content to convert smilies from text.
2834 * @return string Converted content with text smilies replaced with images.
2835 */
2836function convert_smilies( $text ) {
2837 global $wp_smiliessearch;
2838 $output = '';
2839 if ( get_option( 'use_smilies' ) && ! empty( $wp_smiliessearch ) ) {
2840 // HTML loop taken from texturize function, could possible be consolidated
2841 $textarr = preg_split( '/(<.*>)/U', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); // capture the tags as well as in between
2842 $stop = count( $textarr );// loop stuff
2843
2844 // Ignore proessing of specific tags
2845 $tags_to_ignore = 'code|pre|style|script|textarea';
2846 $ignore_block_element = '';
2847
2848 for ( $i = 0; $i < $stop; $i++ ) {
2849 $content = $textarr[$i];
2850
2851 // If we're in an ignore block, wait until we find its closing tag
2852 if ( '' == $ignore_block_element && preg_match( '/^<(' . $tags_to_ignore . ')>/', $content, $matches ) ) {
2853 $ignore_block_element = $matches[1];
2854 }
2855
2856 // If it's not a tag and not in ignore block
2857 if ( '' == $ignore_block_element && strlen( $content ) > 0 && '<' != $content[0] ) {
2858 $content = preg_replace_callback( $wp_smiliessearch, 'translate_smiley', $content );
2859 }
2860
2861 // did we exit ignore block
2862 if ( '' != $ignore_block_element && '</' . $ignore_block_element . '>' == $content ) {
2863 $ignore_block_element = '';
2864 }
2865
2866 $output .= $content;
2867 }
2868 } else {
2869 // return default text.
2870 $output = $text;
2871 }
2872 return $output;
2873}
2874
2875/**
2876 * Verifies that an email is valid.
2877 *
2878 * Does not grok i18n domains. Not RFC compliant.
2879 *
2880 * @since 0.71
2881 *
2882 * @param string $email Email address to verify.
2883 * @param bool $deprecated Deprecated.
2884 * @return string|bool Either false or the valid email address.
2885 */
2886function is_email( $email, $deprecated = false ) {
2887 if ( ! empty( $deprecated ) )
2888 _deprecated_argument( __FUNCTION__, '3.0.0' );
2889
2890 // Test for the minimum length the email can be
2891 if ( strlen( $email ) < 6 ) {
2892 /**
2893 * Filters whether an email address is valid.
2894 *
2895 * This filter is evaluated under several different contexts, such as 'email_too_short',
2896 * 'email_no_at', 'local_invalid_chars', 'domain_period_sequence', 'domain_period_limits',
2897 * 'domain_no_periods', 'sub_hyphen_limits', 'sub_invalid_chars', or no specific context.
2898 *
2899 * @since 2.8.0
2900 *
2901 * @param bool $is_email Whether the email address has passed the is_email() checks. Default false.
2902 * @param string $email The email address being checked.
2903 * @param string $context Context under which the email was tested.
2904 */
2905 return apply_filters( 'is_email', false, $email, 'email_too_short' );
2906 }
2907
2908 // Test for an @ character after the first position
2909 if ( strpos( $email, '@', 1 ) === false ) {
2910 /** This filter is documented in wp-includes/formatting.php */
2911 return apply_filters( 'is_email', false, $email, 'email_no_at' );
2912 }
2913
2914 // Split out the local and domain parts
2915 list( $local, $domain ) = explode( '@', $email, 2 );
2916
2917 // LOCAL PART
2918 // Test for invalid characters
2919 if ( !preg_match( '/^[a-zA-Z0-9!#$%&\'*+\/=?^_`{|}~\.-]+$/', $local ) ) {
2920 /** This filter is documented in wp-includes/formatting.php */
2921 return apply_filters( 'is_email', false, $email, 'local_invalid_chars' );
2922 }
2923
2924 // DOMAIN PART
2925 // Test for sequences of periods
2926 if ( preg_match( '/\.{2,}/', $domain ) ) {
2927 /** This filter is documented in wp-includes/formatting.php */
2928 return apply_filters( 'is_email', false, $email, 'domain_period_sequence' );
2929 }
2930
2931 // Test for leading and trailing periods and whitespace
2932 if ( trim( $domain, " \t\n\r\0\x0B." ) !== $domain ) {
2933 /** This filter is documented in wp-includes/formatting.php */
2934 return apply_filters( 'is_email', false, $email, 'domain_period_limits' );
2935 }
2936
2937 // Split the domain into subs
2938 $subs = explode( '.', $domain );
2939
2940 // Assume the domain will have at least two subs
2941 if ( 2 > count( $subs ) ) {
2942 /** This filter is documented in wp-includes/formatting.php */
2943 return apply_filters( 'is_email', false, $email, 'domain_no_periods' );
2944 }
2945
2946 // Loop through each sub
2947 foreach ( $subs as $sub ) {
2948 // Test for leading and trailing hyphens and whitespace
2949 if ( trim( $sub, " \t\n\r\0\x0B-" ) !== $sub ) {
2950 /** This filter is documented in wp-includes/formatting.php */
2951 return apply_filters( 'is_email', false, $email, 'sub_hyphen_limits' );
2952 }
2953
2954 // Test for invalid characters
2955 if ( !preg_match('/^[a-z0-9-]+$/i', $sub ) ) {
2956 /** This filter is documented in wp-includes/formatting.php */
2957 return apply_filters( 'is_email', false, $email, 'sub_invalid_chars' );
2958 }
2959 }
2960
2961 // Congratulations your email made it!
2962 /** This filter is documented in wp-includes/formatting.php */
2963 return apply_filters( 'is_email', $email, $email, null );
2964}
2965
2966/**
2967 * Convert to ASCII from email subjects.
2968 *
2969 * @since 1.2.0
2970 *
2971 * @param string $string Subject line
2972 * @return string Converted string to ASCII
2973 */
2974function wp_iso_descrambler( $string ) {
2975 /* this may only work with iso-8859-1, I'm afraid */
2976 if (!preg_match('#\=\?(.+)\?Q\?(.+)\?\=#i', $string, $matches)) {
2977 return $string;
2978 } else {
2979 $subject = str_replace('_', ' ', $matches[2]);
2980 return preg_replace_callback( '#\=([0-9a-f]{2})#i', '_wp_iso_convert', $subject );
2981 }
2982}
2983
2984/**
2985 * Helper function to convert hex encoded chars to ASCII
2986 *
2987 * @since 3.1.0
2988 * @access private
2989 *
2990 * @param array $match The preg_replace_callback matches array
2991 * @return string Converted chars
2992 */
2993function _wp_iso_convert( $match ) {
2994 return chr( hexdec( strtolower( $match[1] ) ) );
2995}
2996
2997/**
2998 * Returns a date in the GMT equivalent.
2999 *
3000 * Requires and returns a date in the Y-m-d H:i:s format. If there is a
3001 * timezone_string available, the date is assumed to be in that timezone,
3002 * otherwise it simply subtracts the value of the 'gmt_offset' option. Return
3003 * format can be overridden using the $format parameter.
3004 *
3005 * @since 1.2.0
3006 *
3007 * @param string $string The date to be converted.
3008 * @param string $format The format string for the returned date (default is Y-m-d H:i:s)
3009 * @return string GMT version of the date provided.
3010 */
3011function get_gmt_from_date( $string, $format = 'Y-m-d H:i:s' ) {
3012 $tz = get_option( 'timezone_string' );
3013 if ( $tz ) {
3014 $datetime = date_create( $string, new DateTimeZone( $tz ) );
3015 if ( ! $datetime ) {
3016 return gmdate( $format, 0 );
3017 }
3018 $datetime->setTimezone( new DateTimeZone( 'UTC' ) );
3019 $string_gmt = $datetime->format( $format );
3020 } else {
3021 if ( ! preg_match( '#([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2}) ([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})#', $string, $matches ) ) {
3022 $datetime = strtotime( $string );
3023 if ( false === $datetime ) {
3024 return gmdate( $format, 0 );
3025 }
3026 return gmdate( $format, $datetime );
3027 }
3028 $string_time = gmmktime( $matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1] );
3029 $string_gmt = gmdate( $format, $string_time - get_option( 'gmt_offset' ) * HOUR_IN_SECONDS );
3030 }
3031 return $string_gmt;
3032}
3033
3034/**
3035 * Converts a GMT date into the correct format for the blog.
3036 *
3037 * Requires and returns a date in the Y-m-d H:i:s format. If there is a
3038 * timezone_string available, the returned date is in that timezone, otherwise
3039 * it simply adds the value of gmt_offset. Return format can be overridden
3040 * using the $format parameter
3041 *
3042 * @since 1.2.0
3043 *
3044 * @param string $string The date to be converted.
3045 * @param string $format The format string for the returned date (default is Y-m-d H:i:s)
3046 * @return string Formatted date relative to the timezone / GMT offset.
3047 */
3048function get_date_from_gmt( $string, $format = 'Y-m-d H:i:s' ) {
3049 $tz = get_option( 'timezone_string' );
3050 if ( $tz ) {
3051 $datetime = date_create( $string, new DateTimeZone( 'UTC' ) );
3052 if ( ! $datetime )
3053 return date( $format, 0 );
3054 $datetime->setTimezone( new DateTimeZone( $tz ) );
3055 $string_localtime = $datetime->format( $format );
3056 } else {
3057 if ( ! preg_match('#([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2}) ([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})#', $string, $matches) )
3058 return date( $format, 0 );
3059 $string_time = gmmktime( $matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1] );
3060 $string_localtime = gmdate( $format, $string_time + get_option( 'gmt_offset' ) * HOUR_IN_SECONDS );
3061 }
3062 return $string_localtime;
3063}
3064
3065/**
3066 * Computes an offset in seconds from an iso8601 timezone.
3067 *
3068 * @since 1.5.0
3069 *
3070 * @param string $timezone Either 'Z' for 0 offset or '±hhmm'.
3071 * @return int|float The offset in seconds.
3072 */
3073function iso8601_timezone_to_offset( $timezone ) {
3074 // $timezone is either 'Z' or '[+|-]hhmm'
3075 if ($timezone == 'Z') {
3076 $offset = 0;
3077 } else {
3078 $sign = (substr($timezone, 0, 1) == '+') ? 1 : -1;
3079 $hours = intval(substr($timezone, 1, 2));
3080 $minutes = intval(substr($timezone, 3, 4)) / 60;
3081 $offset = $sign * HOUR_IN_SECONDS * ($hours + $minutes);
3082 }
3083 return $offset;
3084}
3085
3086/**
3087 * Converts an iso8601 date to MySQL DateTime format used by post_date[_gmt].
3088 *
3089 * @since 1.5.0
3090 *
3091 * @param string $date_string Date and time in ISO 8601 format {@link https://en.wikipedia.org/wiki/ISO_8601}.
3092 * @param string $timezone Optional. If set to GMT returns the time minus gmt_offset. Default is 'user'.
3093 * @return string The date and time in MySQL DateTime format - Y-m-d H:i:s.
3094 */
3095function iso8601_to_datetime( $date_string, $timezone = 'user' ) {
3096 $timezone = strtolower($timezone);
3097
3098 if ($timezone == 'gmt') {
3099
3100 preg_match('#([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(Z|[\+|\-][0-9]{2,4}){0,1}#', $date_string, $date_bits);
3101
3102 if (!empty($date_bits[7])) { // we have a timezone, so let's compute an offset
3103 $offset = iso8601_timezone_to_offset($date_bits[7]);
3104 } else { // we don't have a timezone, so we assume user local timezone (not server's!)
3105 $offset = HOUR_IN_SECONDS * get_option('gmt_offset');
3106 }
3107
3108 $timestamp = gmmktime($date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1]);
3109 $timestamp -= $offset;
3110
3111 return gmdate('Y-m-d H:i:s', $timestamp);
3112
3113 } elseif ($timezone == 'user') {
3114 return preg_replace('#([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(Z|[\+|\-][0-9]{2,4}){0,1}#', '$1-$2-$3 $4:$5:$6', $date_string);
3115 }
3116}
3117
3118/**
3119 * Strips out all characters that are not allowable in an email.
3120 *
3121 * @since 1.5.0
3122 *
3123 * @param string $email Email address to filter.
3124 * @return string Filtered email address.
3125 */
3126function sanitize_email( $email ) {
3127 // Test for the minimum length the email can be
3128 if ( strlen( $email ) < 6 ) {
3129 /**
3130 * Filters a sanitized email address.
3131 *
3132 * This filter is evaluated under several contexts, including 'email_too_short',
3133 * 'email_no_at', 'local_invalid_chars', 'domain_period_sequence', 'domain_period_limits',
3134 * 'domain_no_periods', 'domain_no_valid_subs', or no context.
3135 *
3136 * @since 2.8.0
3137 *
3138 * @param string $email The sanitized email address.
3139 * @param string $email The email address, as provided to sanitize_email().
3140 * @param string $message A message to pass to the user.
3141 */
3142 return apply_filters( 'sanitize_email', '', $email, 'email_too_short' );
3143 }
3144
3145 // Test for an @ character after the first position
3146 if ( strpos( $email, '@', 1 ) === false ) {
3147 /** This filter is documented in wp-includes/formatting.php */
3148 return apply_filters( 'sanitize_email', '', $email, 'email_no_at' );
3149 }
3150
3151 // Split out the local and domain parts
3152 list( $local, $domain ) = explode( '@', $email, 2 );
3153
3154 // LOCAL PART
3155 // Test for invalid characters
3156 $local = preg_replace( '/[^a-zA-Z0-9!#$%&\'*+\/=?^_`{|}~\.-]/', '', $local );
3157 if ( '' === $local ) {
3158 /** This filter is documented in wp-includes/formatting.php */
3159 return apply_filters( 'sanitize_email', '', $email, 'local_invalid_chars' );
3160 }
3161
3162 // DOMAIN PART
3163 // Test for sequences of periods
3164 $domain = preg_replace( '/\.{2,}/', '', $domain );
3165 if ( '' === $domain ) {
3166 /** This filter is documented in wp-includes/formatting.php */
3167 return apply_filters( 'sanitize_email', '', $email, 'domain_period_sequence' );
3168 }
3169
3170 // Test for leading and trailing periods and whitespace
3171 $domain = trim( $domain, " \t\n\r\0\x0B." );
3172 if ( '' === $domain ) {
3173 /** This filter is documented in wp-includes/formatting.php */
3174 return apply_filters( 'sanitize_email', '', $email, 'domain_period_limits' );
3175 }
3176
3177 // Split the domain into subs
3178 $subs = explode( '.', $domain );
3179
3180 // Assume the domain will have at least two subs
3181 if ( 2 > count( $subs ) ) {
3182 /** This filter is documented in wp-includes/formatting.php */
3183 return apply_filters( 'sanitize_email', '', $email, 'domain_no_periods' );
3184 }
3185
3186 // Create an array that will contain valid subs
3187 $new_subs = array();
3188
3189 // Loop through each sub
3190 foreach ( $subs as $sub ) {
3191 // Test for leading and trailing hyphens
3192 $sub = trim( $sub, " \t\n\r\0\x0B-" );
3193
3194 // Test for invalid characters
3195 $sub = preg_replace( '/[^a-z0-9-]+/i', '', $sub );
3196
3197 // If there's anything left, add it to the valid subs
3198 if ( '' !== $sub ) {
3199 $new_subs[] = $sub;
3200 }
3201 }
3202
3203 // If there aren't 2 or more valid subs
3204 if ( 2 > count( $new_subs ) ) {
3205 /** This filter is documented in wp-includes/formatting.php */
3206 return apply_filters( 'sanitize_email', '', $email, 'domain_no_valid_subs' );
3207 }
3208
3209 // Join valid subs into the new domain
3210 $domain = join( '.', $new_subs );
3211
3212 // Put the email back together
3213 $email = $local . '@' . $domain;
3214
3215 // Congratulations your email made it!
3216 /** This filter is documented in wp-includes/formatting.php */
3217 return apply_filters( 'sanitize_email', $email, $email, null );
3218}
3219
3220/**
3221 * Determines the difference between two timestamps.
3222 *
3223 * The difference is returned in a human readable format such as "1 hour",
3224 * "5 mins", "2 days".
3225 *
3226 * @since 1.5.0
3227 *
3228 * @param int $from Unix timestamp from which the difference begins.
3229 * @param int $to Optional. Unix timestamp to end the time difference. Default becomes time() if not set.
3230 * @return string Human readable time difference.
3231 */
3232function human_time_diff( $from, $to = '' ) {
3233 if ( empty( $to ) ) {
3234 $to = time();
3235 }
3236
3237 $diff = (int) abs( $to - $from );
3238
3239 if ( $diff < HOUR_IN_SECONDS ) {
3240 $mins = round( $diff / MINUTE_IN_SECONDS );
3241 if ( $mins <= 1 )
3242 $mins = 1;
3243 /* translators: Time difference between two dates, in minutes (min=minute). 1: Number of minutes */
3244 $since = sprintf( _n( '%s min', '%s mins', $mins ), $mins );
3245 } elseif ( $diff < DAY_IN_SECONDS && $diff >= HOUR_IN_SECONDS ) {
3246 $hours = round( $diff / HOUR_IN_SECONDS );
3247 if ( $hours <= 1 )
3248 $hours = 1;
3249 /* translators: Time difference between two dates, in hours. 1: Number of hours */
3250 $since = sprintf( _n( '%s hour', '%s hours', $hours ), $hours );
3251 } elseif ( $diff < WEEK_IN_SECONDS && $diff >= DAY_IN_SECONDS ) {
3252 $days = round( $diff / DAY_IN_SECONDS );
3253 if ( $days <= 1 )
3254 $days = 1;
3255 /* translators: Time difference between two dates, in days. 1: Number of days */
3256 $since = sprintf( _n( '%s day', '%s days', $days ), $days );
3257 } elseif ( $diff < MONTH_IN_SECONDS && $diff >= WEEK_IN_SECONDS ) {
3258 $weeks = round( $diff / WEEK_IN_SECONDS );
3259 if ( $weeks <= 1 )
3260 $weeks = 1;
3261 /* translators: Time difference between two dates, in weeks. 1: Number of weeks */
3262 $since = sprintf( _n( '%s week', '%s weeks', $weeks ), $weeks );
3263 } elseif ( $diff < YEAR_IN_SECONDS && $diff >= MONTH_IN_SECONDS ) {
3264 $months = round( $diff / MONTH_IN_SECONDS );
3265 if ( $months <= 1 )
3266 $months = 1;
3267 /* translators: Time difference between two dates, in months. 1: Number of months */
3268 $since = sprintf( _n( '%s month', '%s months', $months ), $months );
3269 } elseif ( $diff >= YEAR_IN_SECONDS ) {
3270 $years = round( $diff / YEAR_IN_SECONDS );
3271 if ( $years <= 1 )
3272 $years = 1;
3273 /* translators: Time difference between two dates, in years. 1: Number of years */
3274 $since = sprintf( _n( '%s year', '%s years', $years ), $years );
3275 }
3276
3277 /**
3278 * Filters the human readable difference between two timestamps.
3279 *
3280 * @since 4.0.0
3281 *
3282 * @param string $since The difference in human readable text.
3283 * @param int $diff The difference in seconds.
3284 * @param int $from Unix timestamp from which the difference begins.
3285 * @param int $to Unix timestamp to end the time difference.
3286 */
3287 return apply_filters( 'human_time_diff', $since, $diff, $from, $to );
3288}
3289
3290/**
3291 * Generates an excerpt from the content, if needed.
3292 *
3293 * The excerpt word amount will be 55 words and if the amount is greater than
3294 * that, then the string ' […]' will be appended to the excerpt. If the string
3295 * is less than 55 words, then the content will be returned as is.
3296 *
3297 * The 55 word limit can be modified by plugins/themes using the {@see 'excerpt_length'} filter
3298 * The ' […]' string can be modified by plugins/themes using the {@see 'excerpt_more'} filter
3299 *
3300 * @since 1.5.0
3301 *
3302 * @param string $text Optional. The excerpt. If set to empty, an excerpt is generated.
3303 * @return string The excerpt.
3304 */
3305function wp_trim_excerpt( $text = '' ) {
3306 $raw_excerpt = $text;
3307 if ( '' == $text ) {
3308 $text = get_the_content('');
3309
3310 $text = strip_shortcodes( $text );
3311
3312 /** This filter is documented in wp-includes/post-template.php */
3313 $text = apply_filters( 'the_content', $text );
3314 $text = str_replace(']]>', ']]>', $text);
3315
3316 /**
3317 * Filters the number of words in an excerpt.
3318 *
3319 * @since 2.7.0
3320 *
3321 * @param int $number The number of words. Default 55.
3322 */
3323 $excerpt_length = apply_filters( 'excerpt_length', 55 );
3324 /**
3325 * Filters the string in the "more" link displayed after a trimmed excerpt.
3326 *
3327 * @since 2.9.0
3328 *
3329 * @param string $more_string The string shown within the more link.
3330 */
3331 $excerpt_more = apply_filters( 'excerpt_more', ' ' . '[…]' );
3332 $text = wp_trim_words( $text, $excerpt_length, $excerpt_more );
3333 }
3334 /**
3335 * Filters the trimmed excerpt string.
3336 *
3337 * @since 2.8.0
3338 *
3339 * @param string $text The trimmed text.
3340 * @param string $raw_excerpt The text prior to trimming.
3341 */
3342 return apply_filters( 'wp_trim_excerpt', $text, $raw_excerpt );
3343}
3344
3345/**
3346 * Trims text to a certain number of words.
3347 *
3348 * This function is localized. For languages that count 'words' by the individual
3349 * character (such as East Asian languages), the $num_words argument will apply
3350 * to the number of individual characters.
3351 *
3352 * @since 3.3.0
3353 *
3354 * @param string $text Text to trim.
3355 * @param int $num_words Number of words. Default 55.
3356 * @param string $more Optional. What to append if $text needs to be trimmed. Default '…'.
3357 * @return string Trimmed text.
3358 */
3359function wp_trim_words( $text, $num_words = 55, $more = null ) {
3360 if ( null === $more ) {
3361 $more = __( '…' );
3362 }
3363
3364 $original_text = $text;
3365 $text = wp_strip_all_tags( $text );
3366
3367 /*
3368 * translators: If your word count is based on single characters (e.g. East Asian characters),
3369 * enter 'characters_excluding_spaces' or 'characters_including_spaces'. Otherwise, enter 'words'.
3370 * Do not translate into your own language.
3371 */
3372 if ( strpos( _x( 'words', 'Word count type. Do not translate!' ), 'characters' ) === 0 && preg_match( '/^utf\-?8$/i', get_option( 'blog_charset' ) ) ) {
3373 $text = trim( preg_replace( "/[\n\r\t ]+/", ' ', $text ), ' ' );
3374 preg_match_all( '/./u', $text, $words_array );
3375 $words_array = array_slice( $words_array[0], 0, $num_words + 1 );
3376 $sep = '';
3377 } else {
3378 $words_array = preg_split( "/[\n\r\t ]+/", $text, $num_words + 1, PREG_SPLIT_NO_EMPTY );
3379 $sep = ' ';
3380 }
3381
3382 if ( count( $words_array ) > $num_words ) {
3383 array_pop( $words_array );
3384 $text = implode( $sep, $words_array );
3385 $text = $text . $more;
3386 } else {
3387 $text = implode( $sep, $words_array );
3388 }
3389
3390 /**
3391 * Filters the text content after words have been trimmed.
3392 *
3393 * @since 3.3.0
3394 *
3395 * @param string $text The trimmed text.
3396 * @param int $num_words The number of words to trim the text to. Default 55.
3397 * @param string $more An optional string to append to the end of the trimmed text, e.g. ….
3398 * @param string $original_text The text before it was trimmed.
3399 */
3400 return apply_filters( 'wp_trim_words', $text, $num_words, $more, $original_text );
3401}
3402
3403/**
3404 * Converts named entities into numbered entities.
3405 *
3406 * @since 1.5.1
3407 *
3408 * @param string $text The text within which entities will be converted.
3409 * @return string Text with converted entities.
3410 */
3411function ent2ncr( $text ) {
3412
3413 /**
3414 * Filters text before named entities are converted into numbered entities.
3415 *
3416 * A non-null string must be returned for the filter to be evaluated.
3417 *
3418 * @since 3.3.0
3419 *
3420 * @param null $converted_text The text to be converted. Default null.
3421 * @param string $text The text prior to entity conversion.
3422 */
3423 $filtered = apply_filters( 'pre_ent2ncr', null, $text );
3424 if ( null !== $filtered )
3425 return $filtered;
3426
3427 $to_ncr = array(
3428 '"' => '"',
3429 '&' => '&',
3430 '<' => '<',
3431 '>' => '>',
3432 '|' => '|',
3433 ' ' => ' ',
3434 '¡' => '¡',
3435 '¢' => '¢',
3436 '£' => '£',
3437 '¤' => '¤',
3438 '¥' => '¥',
3439 '¦' => '¦',
3440 '&brkbar;' => '¦',
3441 '§' => '§',
3442 '¨' => '¨',
3443 '¨' => '¨',
3444 '©' => '©',
3445 'ª' => 'ª',
3446 '«' => '«',
3447 '¬' => '¬',
3448 '­' => '­',
3449 '®' => '®',
3450 '¯' => '¯',
3451 '&hibar;' => '¯',
3452 '°' => '°',
3453 '±' => '±',
3454 '²' => '²',
3455 '³' => '³',
3456 '´' => '´',
3457 'µ' => 'µ',
3458 '¶' => '¶',
3459 '·' => '·',
3460 '¸' => '¸',
3461 '¹' => '¹',
3462 'º' => 'º',
3463 '»' => '»',
3464 '¼' => '¼',
3465 '½' => '½',
3466 '¾' => '¾',
3467 '¿' => '¿',
3468 'À' => 'À',
3469 'Á' => 'Á',
3470 'Â' => 'Â',
3471 'Ã' => 'Ã',
3472 'Ä' => 'Ä',
3473 'Å' => 'Å',
3474 'Æ' => 'Æ',
3475 'Ç' => 'Ç',
3476 'È' => 'È',
3477 'É' => 'É',
3478 'Ê' => 'Ê',
3479 'Ë' => 'Ë',
3480 'Ì' => 'Ì',
3481 'Í' => 'Í',
3482 'Î' => 'Î',
3483 'Ï' => 'Ï',
3484 'Ð' => 'Ð',
3485 'Ñ' => 'Ñ',
3486 'Ò' => 'Ò',
3487 'Ó' => 'Ó',
3488 'Ô' => 'Ô',
3489 'Õ' => 'Õ',
3490 'Ö' => 'Ö',
3491 '×' => '×',
3492 'Ø' => 'Ø',
3493 'Ù' => 'Ù',
3494 'Ú' => 'Ú',
3495 'Û' => 'Û',
3496 'Ü' => 'Ü',
3497 'Ý' => 'Ý',
3498 'Þ' => 'Þ',
3499 'ß' => 'ß',
3500 'à' => 'à',
3501 'á' => 'á',
3502 'â' => 'â',
3503 'ã' => 'ã',
3504 'ä' => 'ä',
3505 'å' => 'å',
3506 'æ' => 'æ',
3507 'ç' => 'ç',
3508 'è' => 'è',
3509 'é' => 'é',
3510 'ê' => 'ê',
3511 'ë' => 'ë',
3512 'ì' => 'ì',
3513 'í' => 'í',
3514 'î' => 'î',
3515 'ï' => 'ï',
3516 'ð' => 'ð',
3517 'ñ' => 'ñ',
3518 'ò' => 'ò',
3519 'ó' => 'ó',
3520 'ô' => 'ô',
3521 'õ' => 'õ',
3522 'ö' => 'ö',
3523 '÷' => '÷',
3524 'ø' => 'ø',
3525 'ù' => 'ù',
3526 'ú' => 'ú',
3527 'û' => 'û',
3528 'ü' => 'ü',
3529 'ý' => 'ý',
3530 'þ' => 'þ',
3531 'ÿ' => 'ÿ',
3532 'Œ' => 'Œ',
3533 'œ' => 'œ',
3534 'Š' => 'Š',
3535 'š' => 'š',
3536 'Ÿ' => 'Ÿ',
3537 'ƒ' => 'ƒ',
3538 'ˆ' => 'ˆ',
3539 '˜' => '˜',
3540 'Α' => 'Α',
3541 'Β' => 'Β',
3542 'Γ' => 'Γ',
3543 'Δ' => 'Δ',
3544 'Ε' => 'Ε',
3545 'Ζ' => 'Ζ',
3546 'Η' => 'Η',
3547 'Θ' => 'Θ',
3548 'Ι' => 'Ι',
3549 'Κ' => 'Κ',
3550 'Λ' => 'Λ',
3551 'Μ' => 'Μ',
3552 'Ν' => 'Ν',
3553 'Ξ' => 'Ξ',
3554 'Ο' => 'Ο',
3555 'Π' => 'Π',
3556 'Ρ' => 'Ρ',
3557 'Σ' => 'Σ',
3558 'Τ' => 'Τ',
3559 'Υ' => 'Υ',
3560 'Φ' => 'Φ',
3561 'Χ' => 'Χ',
3562 'Ψ' => 'Ψ',
3563 'Ω' => 'Ω',
3564 'α' => 'α',
3565 'β' => 'β',
3566 'γ' => 'γ',
3567 'δ' => 'δ',
3568 'ε' => 'ε',
3569 'ζ' => 'ζ',
3570 'η' => 'η',
3571 'θ' => 'θ',
3572 'ι' => 'ι',
3573 'κ' => 'κ',
3574 'λ' => 'λ',
3575 'μ' => 'μ',
3576 'ν' => 'ν',
3577 'ξ' => 'ξ',
3578 'ο' => 'ο',
3579 'π' => 'π',
3580 'ρ' => 'ρ',
3581 'ς' => 'ς',
3582 'σ' => 'σ',
3583 'τ' => 'τ',
3584 'υ' => 'υ',
3585 'φ' => 'φ',
3586 'χ' => 'χ',
3587 'ψ' => 'ψ',
3588 'ω' => 'ω',
3589 'ϑ' => 'ϑ',
3590 'ϒ' => 'ϒ',
3591 'ϖ' => 'ϖ',
3592 ' ' => ' ',
3593 ' ' => ' ',
3594 ' ' => ' ',
3595 '‌' => '‌',
3596 '‍' => '‍',
3597 '‎' => '‎',
3598 '‏' => '‏',
3599 '–' => '–',
3600 '—' => '—',
3601 '‘' => '‘',
3602 '’' => '’',
3603 '‚' => '‚',
3604 '“' => '“',
3605 '”' => '”',
3606 '„' => '„',
3607 '†' => '†',
3608 '‡' => '‡',
3609 '•' => '•',
3610 '…' => '…',
3611 '‰' => '‰',
3612 '′' => '′',
3613 '″' => '″',
3614 '‹' => '‹',
3615 '›' => '›',
3616 '‾' => '‾',
3617 '⁄' => '⁄',
3618 '€' => '€',
3619 'ℑ' => 'ℑ',
3620 '℘' => '℘',
3621 'ℜ' => 'ℜ',
3622 '™' => '™',
3623 'ℵ' => 'ℵ',
3624 '↵' => '↵',
3625 '⇐' => '⇐',
3626 '⇑' => '⇑',
3627 '⇒' => '⇒',
3628 '⇓' => '⇓',
3629 '⇔' => '⇔',
3630 '∀' => '∀',
3631 '∂' => '∂',
3632 '∃' => '∃',
3633 '∅' => '∅',
3634 '∇' => '∇',
3635 '∈' => '∈',
3636 '∉' => '∉',
3637 '∋' => '∋',
3638 '∏' => '∏',
3639 '∑' => '∑',
3640 '−' => '−',
3641 '∗' => '∗',
3642 '√' => '√',
3643 '∝' => '∝',
3644 '∞' => '∞',
3645 '∠' => '∠',
3646 '∧' => '∧',
3647 '∨' => '∨',
3648 '∩' => '∩',
3649 '∪' => '∪',
3650 '∫' => '∫',
3651 '∴' => '∴',
3652 '∼' => '∼',
3653 '≅' => '≅',
3654 '≈' => '≈',
3655 '≠' => '≠',
3656 '≡' => '≡',
3657 '≤' => '≤',
3658 '≥' => '≥',
3659 '⊂' => '⊂',
3660 '⊃' => '⊃',
3661 '⊄' => '⊄',
3662 '⊆' => '⊆',
3663 '⊇' => '⊇',
3664 '⊕' => '⊕',
3665 '⊗' => '⊗',
3666 '⊥' => '⊥',
3667 '⋅' => '⋅',
3668 '⌈' => '⌈',
3669 '⌉' => '⌉',
3670 '⌊' => '⌊',
3671 '⌋' => '⌋',
3672 '⟨' => '〈',
3673 '⟩' => '〉',
3674 '←' => '←',
3675 '↑' => '↑',
3676 '→' => '→',
3677 '↓' => '↓',
3678 '↔' => '↔',
3679 '◊' => '◊',
3680 '♠' => '♠',
3681 '♣' => '♣',
3682 '♥' => '♥',
3683 '♦' => '♦'
3684 );
3685
3686 return str_replace( array_keys($to_ncr), array_values($to_ncr), $text );
3687}
3688
3689/**
3690 * Formats text for the editor.
3691 *
3692 * Generally the browsers treat everything inside a textarea as text, but
3693 * it is still a good idea to HTML entity encode `<`, `>` and `&` in the content.
3694 *
3695 * The filter {@see 'format_for_editor'} is applied here. If `$text` is empty the
3696 * filter will be applied to an empty string.
3697 *
3698 * @since 4.3.0
3699 *
3700 * @see _WP_Editors::editor()
3701 *
3702 * @param string $text The text to be formatted.
3703 * @param string $default_editor The default editor for the current user.
3704 * It is usually either 'html' or 'tinymce'.
3705 * @return string The formatted text after filter is applied.
3706 */
3707function format_for_editor( $text, $default_editor = null ) {
3708 if ( $text ) {
3709 $text = htmlspecialchars( $text, ENT_NOQUOTES, get_option( 'blog_charset' ) );
3710 }
3711
3712 /**
3713 * Filters the text after it is formatted for the editor.
3714 *
3715 * @since 4.3.0
3716 *
3717 * @param string $text The formatted text.
3718 * @param string $default_editor The default editor for the current user.
3719 * It is usually either 'html' or 'tinymce'.
3720 */
3721 return apply_filters( 'format_for_editor', $text, $default_editor );
3722}
3723
3724/**
3725 * Perform a deep string replace operation to ensure the values in $search are no longer present
3726 *
3727 * Repeats the replacement operation until it no longer replaces anything so as to remove "nested" values
3728 * e.g. $subject = '%0%0%0DDD', $search ='%0D', $result ='' rather than the '%0%0DD' that
3729 * str_replace would return
3730 *
3731 * @since 2.8.1
3732 * @access private
3733 *
3734 * @param string|array $search The value being searched for, otherwise known as the needle.
3735 * An array may be used to designate multiple needles.
3736 * @param string $subject The string being searched and replaced on, otherwise known as the haystack.
3737 * @return string The string with the replaced svalues.
3738 */
3739function _deep_replace( $search, $subject ) {
3740 $subject = (string) $subject;
3741
3742 $count = 1;
3743 while ( $count ) {
3744 $subject = str_replace( $search, '', $subject, $count );
3745 }
3746
3747 return $subject;
3748}
3749
3750/**
3751 * Escapes data for use in a MySQL query.
3752 *
3753 * Usually you should prepare queries using wpdb::prepare().
3754 * Sometimes, spot-escaping is required or useful. One example
3755 * is preparing an array for use in an IN clause.
3756 *
3757 * NOTE: Since 4.8.3, '%' characters will be replaced with a placeholder string,
3758 * this prevents certain SQLi attacks from taking place. This change in behaviour
3759 * may cause issues for code that expects the return value of esc_sql() to be useable
3760 * for other purposes.
3761 *
3762 * @since 2.8.0
3763 *
3764 * @global wpdb $wpdb WordPress database abstraction object.
3765 *
3766 * @param string|array $data Unescaped data
3767 * @return string|array Escaped data
3768 */
3769function esc_sql( $data ) {
3770 global $wpdb;
3771 return $wpdb->_escape( $data );
3772}
3773
3774/**
3775 * Checks and cleans a URL.
3776 *
3777 * A number of characters are removed from the URL. If the URL is for displaying
3778 * (the default behaviour) ampersands are also replaced. The {@see 'clean_url'} filter
3779 * is applied to the returned cleaned URL.
3780 *
3781 * @since 2.8.0
3782 *
3783 * @param string $url The URL to be cleaned.
3784 * @param array $protocols Optional. An array of acceptable protocols.
3785 * Defaults to return value of wp_allowed_protocols()
3786 * @param string $_context Private. Use esc_url_raw() for database usage.
3787 * @return string The cleaned $url after the {@see 'clean_url'} filter is applied.
3788 */
3789function esc_url( $url, $protocols = null, $_context = 'display' ) {
3790 $original_url = $url;
3791
3792 if ( '' == $url )
3793 return $url;
3794
3795 $url = str_replace( ' ', '%20', $url );
3796 $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\[\]\\x80-\\xff]|i', '', $url);
3797
3798 if ( '' === $url ) {
3799 return $url;
3800 }
3801
3802 if ( 0 !== stripos( $url, 'mailto:' ) ) {
3803 $strip = array('%0d', '%0a', '%0D', '%0A');
3804 $url = _deep_replace($strip, $url);
3805 }
3806
3807 $url = str_replace(';//', '://', $url);
3808 /* If the URL doesn't appear to contain a scheme, we
3809 * presume it needs http:// prepended (unless a relative
3810 * link starting with /, # or ? or a php file).
3811 */
3812 if ( strpos($url, ':') === false && ! in_array( $url[0], array( '/', '#', '?' ) ) &&
3813 ! preg_match('/^[a-z0-9-]+?\.php/i', $url) )
3814 $url = 'http://' . $url;
3815
3816 // Replace ampersands and single quotes only when displaying.
3817 if ( 'display' == $_context ) {
3818 $url = wp_kses_normalize_entities( $url );
3819 $url = str_replace( '&', '&', $url );
3820 $url = str_replace( "'", ''', $url );
3821 }
3822
3823 if ( ( false !== strpos( $url, '[' ) ) || ( false !== strpos( $url, ']' ) ) ) {
3824
3825 $parsed = wp_parse_url( $url );
3826 $front = '';
3827
3828 if ( isset( $parsed['scheme'] ) ) {
3829 $front .= $parsed['scheme'] . '://';
3830 } elseif ( '/' === $url[0] ) {
3831 $front .= '//';
3832 }
3833
3834 if ( isset( $parsed['user'] ) ) {
3835 $front .= $parsed['user'];
3836 }
3837
3838 if ( isset( $parsed['pass'] ) ) {
3839 $front .= ':' . $parsed['pass'];
3840 }
3841
3842 if ( isset( $parsed['user'] ) || isset( $parsed['pass'] ) ) {
3843 $front .= '@';
3844 }
3845
3846 if ( isset( $parsed['host'] ) ) {
3847 $front .= $parsed['host'];
3848 }
3849
3850 if ( isset( $parsed['port'] ) ) {
3851 $front .= ':' . $parsed['port'];
3852 }
3853
3854 $end_dirty = str_replace( $front, '', $url );
3855 $end_clean = str_replace( array( '[', ']' ), array( '%5B', '%5D' ), $end_dirty );
3856 $url = str_replace( $end_dirty, $end_clean, $url );
3857
3858 }
3859
3860 if ( '/' === $url[0] ) {
3861 $good_protocol_url = $url;
3862 } else {
3863 if ( ! is_array( $protocols ) )
3864 $protocols = wp_allowed_protocols();
3865 $good_protocol_url = wp_kses_bad_protocol( $url, $protocols );
3866 if ( strtolower( $good_protocol_url ) != strtolower( $url ) )
3867 return '';
3868 }
3869
3870 /**
3871 * Filters a string cleaned and escaped for output as a URL.
3872 *
3873 * @since 2.3.0
3874 *
3875 * @param string $good_protocol_url The cleaned URL to be returned.
3876 * @param string $original_url The URL prior to cleaning.
3877 * @param string $_context If 'display', replace ampersands and single quotes only.
3878 */
3879 return apply_filters( 'clean_url', $good_protocol_url, $original_url, $_context );
3880}
3881
3882/**
3883 * Performs esc_url() for database usage.
3884 *
3885 * @since 2.8.0
3886 *
3887 * @param string $url The URL to be cleaned.
3888 * @param array $protocols An array of acceptable protocols.
3889 * @return string The cleaned URL.
3890 */
3891function esc_url_raw( $url, $protocols = null ) {
3892 return esc_url( $url, $protocols, 'db' );
3893}
3894
3895/**
3896 * Convert entities, while preserving already-encoded entities.
3897 *
3898 * @link https://secure.php.net/htmlentities Borrowed from the PHP Manual user notes.
3899 *
3900 * @since 1.2.2
3901 *
3902 * @param string $myHTML The text to be converted.
3903 * @return string Converted text.
3904 */
3905function htmlentities2( $myHTML ) {
3906 $translation_table = get_html_translation_table( HTML_ENTITIES, ENT_QUOTES );
3907 $translation_table[chr(38)] = '&';
3908 return preg_replace( "/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,3};)/", "&", strtr($myHTML, $translation_table) );
3909}
3910
3911/**
3912 * Escape single quotes, htmlspecialchar " < > &, and fix line endings.
3913 *
3914 * Escapes text strings for echoing in JS. It is intended to be used for inline JS
3915 * (in a tag attribute, for example onclick="..."). Note that the strings have to
3916 * be in single quotes. The {@see 'js_escape'} filter is also applied here.
3917 *
3918 * @since 2.8.0
3919 *
3920 * @param string $text The text to be escaped.
3921 * @return string Escaped text.
3922 */
3923function esc_js( $text ) {
3924 $safe_text = wp_check_invalid_utf8( $text );
3925 $safe_text = _wp_specialchars( $safe_text, ENT_COMPAT );
3926 $safe_text = preg_replace( '/&#(x)?0*(?(1)27|39);?/i', "'", stripslashes( $safe_text ) );
3927 $safe_text = str_replace( "\r", '', $safe_text );
3928 $safe_text = str_replace( "\n", '\\n', addslashes( $safe_text ) );
3929 /**
3930 * Filters a string cleaned and escaped for output in JavaScript.
3931 *
3932 * Text passed to esc_js() is stripped of invalid or special characters,
3933 * and properly slashed for output.
3934 *
3935 * @since 2.0.6
3936 *
3937 * @param string $safe_text The text after it has been escaped.
3938 * @param string $text The text prior to being escaped.
3939 */
3940 return apply_filters( 'js_escape', $safe_text, $text );
3941}
3942
3943/**
3944 * Escaping for HTML blocks.
3945 *
3946 * @since 2.8.0
3947 *
3948 * @param string $text
3949 * @return string
3950 */
3951function esc_html( $text ) {
3952 $safe_text = wp_check_invalid_utf8( $text );
3953 $safe_text = _wp_specialchars( $safe_text, ENT_QUOTES );
3954 /**
3955 * Filters a string cleaned and escaped for output in HTML.
3956 *
3957 * Text passed to esc_html() is stripped of invalid or special characters
3958 * before output.
3959 *
3960 * @since 2.8.0
3961 *
3962 * @param string $safe_text The text after it has been escaped.
3963 * @param string $text The text prior to being escaped.
3964 */
3965 return apply_filters( 'esc_html', $safe_text, $text );
3966}
3967
3968/**
3969 * Escaping for HTML attributes.
3970 *
3971 * @since 2.8.0
3972 *
3973 * @param string $text
3974 * @return string
3975 */
3976function esc_attr( $text ) {
3977 $safe_text = wp_check_invalid_utf8( $text );
3978 $safe_text = _wp_specialchars( $safe_text, ENT_QUOTES );
3979 /**
3980 * Filters a string cleaned and escaped for output in an HTML attribute.
3981 *
3982 * Text passed to esc_attr() is stripped of invalid or special characters
3983 * before output.
3984 *
3985 * @since 2.0.6
3986 *
3987 * @param string $safe_text The text after it has been escaped.
3988 * @param string $text The text prior to being escaped.
3989 */
3990 return apply_filters( 'attribute_escape', $safe_text, $text );
3991}
3992
3993/**
3994 * Escaping for textarea values.
3995 *
3996 * @since 3.1.0
3997 *
3998 * @param string $text
3999 * @return string
4000 */
4001function esc_textarea( $text ) {
4002 $safe_text = htmlspecialchars( $text, ENT_QUOTES, get_option( 'blog_charset' ) );
4003 /**
4004 * Filters a string cleaned and escaped for output in a textarea element.
4005 *
4006 * @since 3.1.0
4007 *
4008 * @param string $safe_text The text after it has been escaped.
4009 * @param string $text The text prior to being escaped.
4010 */
4011 return apply_filters( 'esc_textarea', $safe_text, $text );
4012}
4013
4014/**
4015 * Escape an HTML tag name.
4016 *
4017 * @since 2.5.0
4018 *
4019 * @param string $tag_name
4020 * @return string
4021 */
4022function tag_escape( $tag_name ) {
4023 $safe_tag = strtolower( preg_replace('/[^a-zA-Z0-9_:]/', '', $tag_name) );
4024 /**
4025 * Filters a string cleaned and escaped for output as an HTML tag.
4026 *
4027 * @since 2.8.0
4028 *
4029 * @param string $safe_tag The tag name after it has been escaped.
4030 * @param string $tag_name The text before it was escaped.
4031 */
4032 return apply_filters( 'tag_escape', $safe_tag, $tag_name );
4033}
4034
4035/**
4036 * Convert full URL paths to absolute paths.
4037 *
4038 * Removes the http or https protocols and the domain. Keeps the path '/' at the
4039 * beginning, so it isn't a true relative link, but from the web root base.
4040 *
4041 * @since 2.1.0
4042 * @since 4.1.0 Support was added for relative URLs.
4043 *
4044 * @param string $link Full URL path.
4045 * @return string Absolute path.
4046 */
4047function wp_make_link_relative( $link ) {
4048 return preg_replace( '|^(https?:)?//[^/]+(/?.*)|i', '$2', $link );
4049}
4050
4051/**
4052 * Sanitises various option values based on the nature of the option.
4053 *
4054 * This is basically a switch statement which will pass $value through a number
4055 * of functions depending on the $option.
4056 *
4057 * @since 2.0.5
4058 *
4059 * @global wpdb $wpdb WordPress database abstraction object.
4060 *
4061 * @param string $option The name of the option.
4062 * @param string $value The unsanitised value.
4063 * @return string Sanitized value.
4064 */
4065function sanitize_option( $option, $value ) {
4066 global $wpdb;
4067
4068 $original_value = $value;
4069 $error = '';
4070
4071 switch ( $option ) {
4072 case 'admin_email' :
4073 case 'new_admin_email' :
4074 $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
4075 if ( is_wp_error( $value ) ) {
4076 $error = $value->get_error_message();
4077 } else {
4078 $value = sanitize_email( $value );
4079 if ( ! is_email( $value ) ) {
4080 $error = __( 'The email address entered did not appear to be a valid email address. Please enter a valid email address.' );
4081 }
4082 }
4083 break;
4084
4085 case 'thumbnail_size_w':
4086 case 'thumbnail_size_h':
4087 case 'medium_size_w':
4088 case 'medium_size_h':
4089 case 'medium_large_size_w':
4090 case 'medium_large_size_h':
4091 case 'large_size_w':
4092 case 'large_size_h':
4093 case 'mailserver_port':
4094 case 'comment_max_links':
4095 case 'page_on_front':
4096 case 'page_for_posts':
4097 case 'rss_excerpt_length':
4098 case 'default_category':
4099 case 'default_email_category':
4100 case 'default_link_category':
4101 case 'close_comments_days_old':
4102 case 'comments_per_page':
4103 case 'thread_comments_depth':
4104 case 'users_can_register':
4105 case 'start_of_week':
4106 case 'site_icon':
4107 $value = absint( $value );
4108 break;
4109
4110 case 'posts_per_page':
4111 case 'posts_per_rss':
4112 $value = (int) $value;
4113 if ( empty($value) )
4114 $value = 1;
4115 if ( $value < -1 )
4116 $value = abs($value);
4117 break;
4118
4119 case 'default_ping_status':
4120 case 'default_comment_status':
4121 // Options that if not there have 0 value but need to be something like "closed"
4122 if ( $value == '0' || $value == '')
4123 $value = 'closed';
4124 break;
4125
4126 case 'blogdescription':
4127 case 'blogname':
4128 $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
4129 if ( $value !== $original_value ) {
4130 $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', wp_encode_emoji( $original_value ) );
4131 }
4132
4133 if ( is_wp_error( $value ) ) {
4134 $error = $value->get_error_message();
4135 } else {
4136 $value = esc_html( $value );
4137 }
4138 break;
4139
4140 case 'blog_charset':
4141 $value = preg_replace('/[^a-zA-Z0-9_-]/', '', $value); // strips slashes
4142 break;
4143
4144 case 'blog_public':
4145 // This is the value if the settings checkbox is not checked on POST. Don't rely on this.
4146 if ( null === $value )
4147 $value = 1;
4148 else
4149 $value = intval( $value );
4150 break;
4151
4152 case 'date_format':
4153 case 'time_format':
4154 case 'mailserver_url':
4155 case 'mailserver_login':
4156 case 'mailserver_pass':
4157 case 'upload_path':
4158 $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
4159 if ( is_wp_error( $value ) ) {
4160 $error = $value->get_error_message();
4161 } else {
4162 $value = strip_tags( $value );
4163 $value = wp_kses_data( $value );
4164 }
4165 break;
4166
4167 case 'ping_sites':
4168 $value = explode( "\n", $value );
4169 $value = array_filter( array_map( 'trim', $value ) );
4170 $value = array_filter( array_map( 'esc_url_raw', $value ) );
4171 $value = implode( "\n", $value );
4172 break;
4173
4174 case 'gmt_offset':
4175 $value = preg_replace('/[^0-9:.-]/', '', $value); // strips slashes
4176 break;
4177
4178 case 'siteurl':
4179 $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
4180 if ( is_wp_error( $value ) ) {
4181 $error = $value->get_error_message();
4182 } else {
4183 if ( preg_match( '#http(s?)://(.+)#i', $value ) ) {
4184 $value = esc_url_raw( $value );
4185 } else {
4186 $error = __( 'The WordPress address you entered did not appear to be a valid URL. Please enter a valid URL.' );
4187 }
4188 }
4189 break;
4190
4191 case 'home':
4192 $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
4193 if ( is_wp_error( $value ) ) {
4194 $error = $value->get_error_message();
4195 } else {
4196 if ( preg_match( '#http(s?)://(.+)#i', $value ) ) {
4197 $value = esc_url_raw( $value );
4198 } else {
4199 $error = __( 'The Site address you entered did not appear to be a valid URL. Please enter a valid URL.' );
4200 }
4201 }
4202 break;
4203
4204 case 'WPLANG':
4205 $allowed = get_available_languages();
4206 if ( ! is_multisite() && defined( 'WPLANG' ) && '' !== WPLANG && 'en_US' !== WPLANG ) {
4207 $allowed[] = WPLANG;
4208 }
4209 if ( ! in_array( $value, $allowed ) && ! empty( $value ) ) {
4210 $value = get_option( $option );
4211 }
4212 break;
4213
4214 case 'illegal_names':
4215 $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
4216 if ( is_wp_error( $value ) ) {
4217 $error = $value->get_error_message();
4218 } else {
4219 if ( ! is_array( $value ) )
4220 $value = explode( ' ', $value );
4221
4222 $value = array_values( array_filter( array_map( 'trim', $value ) ) );
4223
4224 if ( ! $value )
4225 $value = '';
4226 }
4227 break;
4228
4229 case 'limited_email_domains':
4230 case 'banned_email_domains':
4231 $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
4232 if ( is_wp_error( $value ) ) {
4233 $error = $value->get_error_message();
4234 } else {
4235 if ( ! is_array( $value ) )
4236 $value = explode( "\n", $value );
4237
4238 $domains = array_values( array_filter( array_map( 'trim', $value ) ) );
4239 $value = array();
4240
4241 foreach ( $domains as $domain ) {
4242 if ( ! preg_match( '/(--|\.\.)/', $domain ) && preg_match( '|^([a-zA-Z0-9-\.])+$|', $domain ) ) {
4243 $value[] = $domain;
4244 }
4245 }
4246 if ( ! $value )
4247 $value = '';
4248 }
4249 break;
4250
4251 case 'timezone_string':
4252 $allowed_zones = timezone_identifiers_list();
4253 if ( ! in_array( $value, $allowed_zones ) && ! empty( $value ) ) {
4254 $error = __( 'The timezone you have entered is not valid. Please select a valid timezone.' );
4255 }
4256 break;
4257
4258 case 'permalink_structure':
4259 case 'category_base':
4260 case 'tag_base':
4261 $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
4262 if ( is_wp_error( $value ) ) {
4263 $error = $value->get_error_message();
4264 } else {
4265 $value = esc_url_raw( $value );
4266 $value = str_replace( 'http://', '', $value );
4267 }
4268
4269 if ( 'permalink_structure' === $option && '' !== $value && ! preg_match( '/%[^\/%]+%/', $value ) ) {
4270 $error = sprintf(
4271 /* translators: %s: Codex URL */
4272 __( 'A structure tag is required when using custom permalinks. <a href="%s">Learn more</a>' ),
4273 __( 'https://codex.wordpress.org/Using_Permalinks#Choosing_your_permalink_structure' )
4274 );
4275 }
4276 break;
4277
4278 case 'default_role' :
4279 if ( ! get_role( $value ) && get_role( 'subscriber' ) )
4280 $value = 'subscriber';
4281 break;
4282
4283 case 'moderation_keys':
4284 case 'blacklist_keys':
4285 $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
4286 if ( is_wp_error( $value ) ) {
4287 $error = $value->get_error_message();
4288 } else {
4289 $value = explode( "\n", $value );
4290 $value = array_filter( array_map( 'trim', $value ) );
4291 $value = array_unique( $value );
4292 $value = implode( "\n", $value );
4293 }
4294 break;
4295 }
4296
4297 if ( ! empty( $error ) ) {
4298 $value = get_option( $option );
4299 if ( function_exists( 'add_settings_error' ) ) {
4300 add_settings_error( $option, "invalid_{$option}", $error );
4301 }
4302 }
4303
4304 /**
4305 * Filters an option value following sanitization.
4306 *
4307 * @since 2.3.0
4308 * @since 4.3.0 Added the `$original_value` parameter.
4309 *
4310 * @param string $value The sanitized option value.
4311 * @param string $option The option name.
4312 * @param string $original_value The original value passed to the function.
4313 */
4314 return apply_filters( "sanitize_option_{$option}", $value, $option, $original_value );
4315}
4316
4317/**
4318 * Maps a function to all non-iterable elements of an array or an object.
4319 *
4320 * This is similar to `array_walk_recursive()` but acts upon objects too.
4321 *
4322 * @since 4.4.0
4323 *
4324 * @param mixed $value The array, object, or scalar.
4325 * @param callable $callback The function to map onto $value.
4326 * @return mixed The value with the callback applied to all non-arrays and non-objects inside it.
4327 */
4328function map_deep( $value, $callback ) {
4329 if ( is_array( $value ) ) {
4330 foreach ( $value as $index => $item ) {
4331 $value[ $index ] = map_deep( $item, $callback );
4332 }
4333 } elseif ( is_object( $value ) ) {
4334 $object_vars = get_object_vars( $value );
4335 foreach ( $object_vars as $property_name => $property_value ) {
4336 $value->$property_name = map_deep( $property_value, $callback );
4337 }
4338 } else {
4339 $value = call_user_func( $callback, $value );
4340 }
4341
4342 return $value;
4343}
4344
4345/**
4346 * Parses a string into variables to be stored in an array.
4347 *
4348 * Uses {@link https://secure.php.net/parse_str parse_str()} and stripslashes if
4349 * {@link https://secure.php.net/magic_quotes magic_quotes_gpc} is on.
4350 *
4351 * @since 2.2.1
4352 *
4353 * @param string $string The string to be parsed.
4354 * @param array $array Variables will be stored in this array.
4355 */
4356function wp_parse_str( $string, &$array ) {
4357 parse_str( $string, $array );
4358 if ( get_magic_quotes_gpc() )
4359 $array = stripslashes_deep( $array );
4360 /**
4361 * Filters the array of variables derived from a parsed string.
4362 *
4363 * @since 2.3.0
4364 *
4365 * @param array $array The array populated with variables.
4366 */
4367 $array = apply_filters( 'wp_parse_str', $array );
4368}
4369
4370/**
4371 * Convert lone less than signs.
4372 *
4373 * KSES already converts lone greater than signs.
4374 *
4375 * @since 2.3.0
4376 *
4377 * @param string $text Text to be converted.
4378 * @return string Converted text.
4379 */
4380function wp_pre_kses_less_than( $text ) {
4381 return preg_replace_callback('%<[^>]*?((?=<)|>|$)%', 'wp_pre_kses_less_than_callback', $text);
4382}
4383
4384/**
4385 * Callback function used by preg_replace.
4386 *
4387 * @since 2.3.0
4388 *
4389 * @param array $matches Populated by matches to preg_replace.
4390 * @return string The text returned after esc_html if needed.
4391 */
4392function wp_pre_kses_less_than_callback( $matches ) {
4393 if ( false === strpos($matches[0], '>') )
4394 return esc_html($matches[0]);
4395 return $matches[0];
4396}
4397
4398/**
4399 * WordPress implementation of PHP sprintf() with filters.
4400 *
4401 * @since 2.5.0
4402 * @link https://secure.php.net/sprintf
4403 *
4404 * @param string $pattern The string which formatted args are inserted.
4405 * @param mixed $args ,... Arguments to be formatted into the $pattern string.
4406 * @return string The formatted string.
4407 */
4408function wp_sprintf( $pattern ) {
4409 $args = func_get_args();
4410 $len = strlen($pattern);
4411 $start = 0;
4412 $result = '';
4413 $arg_index = 0;
4414 while ( $len > $start ) {
4415 // Last character: append and break
4416 if ( strlen($pattern) - 1 == $start ) {
4417 $result .= substr($pattern, -1);
4418 break;
4419 }
4420
4421 // Literal %: append and continue
4422 if ( substr($pattern, $start, 2) == '%%' ) {
4423 $start += 2;
4424 $result .= '%';
4425 continue;
4426 }
4427
4428 // Get fragment before next %
4429 $end = strpos($pattern, '%', $start + 1);
4430 if ( false === $end )
4431 $end = $len;
4432 $fragment = substr($pattern, $start, $end - $start);
4433
4434 // Fragment has a specifier
4435 if ( $pattern[$start] == '%' ) {
4436 // Find numbered arguments or take the next one in order
4437 if ( preg_match('/^%(\d+)\$/', $fragment, $matches) ) {
4438 $arg = isset($args[$matches[1]]) ? $args[$matches[1]] : '';
4439 $fragment = str_replace("%{$matches[1]}$", '%', $fragment);
4440 } else {
4441 ++$arg_index;
4442 $arg = isset($args[$arg_index]) ? $args[$arg_index] : '';
4443 }
4444
4445 /**
4446 * Filters a fragment from the pattern passed to wp_sprintf().
4447 *
4448 * If the fragment is unchanged, then sprintf() will be run on the fragment.
4449 *
4450 * @since 2.5.0
4451 *
4452 * @param string $fragment A fragment from the pattern.
4453 * @param string $arg The argument.
4454 */
4455 $_fragment = apply_filters( 'wp_sprintf', $fragment, $arg );
4456 if ( $_fragment != $fragment )
4457 $fragment = $_fragment;
4458 else
4459 $fragment = sprintf($fragment, strval($arg) );
4460 }
4461
4462 // Append to result and move to next fragment
4463 $result .= $fragment;
4464 $start = $end;
4465 }
4466 return $result;
4467}
4468
4469/**
4470 * Localize list items before the rest of the content.
4471 *
4472 * The '%l' must be at the first characters can then contain the rest of the
4473 * content. The list items will have ', ', ', and', and ' and ' added depending
4474 * on the amount of list items in the $args parameter.
4475 *
4476 * @since 2.5.0
4477 *
4478 * @param string $pattern Content containing '%l' at the beginning.
4479 * @param array $args List items to prepend to the content and replace '%l'.
4480 * @return string Localized list items and rest of the content.
4481 */
4482function wp_sprintf_l( $pattern, $args ) {
4483 // Not a match
4484 if ( substr($pattern, 0, 2) != '%l' )
4485 return $pattern;
4486
4487 // Nothing to work with
4488 if ( empty($args) )
4489 return '';
4490
4491 /**
4492 * Filters the translated delimiters used by wp_sprintf_l().
4493 * Placeholders (%s) are included to assist translators and then
4494 * removed before the array of strings reaches the filter.
4495 *
4496 * Please note: Ampersands and entities should be avoided here.
4497 *
4498 * @since 2.5.0
4499 *
4500 * @param array $delimiters An array of translated delimiters.
4501 */
4502 $l = apply_filters( 'wp_sprintf_l', array(
4503 /* translators: used to join items in a list with more than 2 items */
4504 'between' => sprintf( __('%s, %s'), '', '' ),
4505 /* translators: used to join last two items in a list with more than 2 times */
4506 'between_last_two' => sprintf( __('%s, and %s'), '', '' ),
4507 /* translators: used to join items in a list with only 2 items */
4508 'between_only_two' => sprintf( __('%s and %s'), '', '' ),
4509 ) );
4510
4511 $args = (array) $args;
4512 $result = array_shift($args);
4513 if ( count($args) == 1 )
4514 $result .= $l['between_only_two'] . array_shift($args);
4515 // Loop when more than two args
4516 $i = count($args);
4517 while ( $i ) {
4518 $arg = array_shift($args);
4519 $i--;
4520 if ( 0 == $i )
4521 $result .= $l['between_last_two'] . $arg;
4522 else
4523 $result .= $l['between'] . $arg;
4524 }
4525 return $result . substr($pattern, 2);
4526}
4527
4528/**
4529 * Safely extracts not more than the first $count characters from html string.
4530 *
4531 * UTF-8, tags and entities safe prefix extraction. Entities inside will *NOT*
4532 * be counted as one character. For example & will be counted as 4, < as
4533 * 3, etc.
4534 *
4535 * @since 2.5.0
4536 *
4537 * @param string $str String to get the excerpt from.
4538 * @param int $count Maximum number of characters to take.
4539 * @param string $more Optional. What to append if $str needs to be trimmed. Defaults to empty string.
4540 * @return string The excerpt.
4541 */
4542function wp_html_excerpt( $str, $count, $more = null ) {
4543 if ( null === $more )
4544 $more = '';
4545 $str = wp_strip_all_tags( $str, true );
4546 $excerpt = mb_substr( $str, 0, $count );
4547 // remove part of an entity at the end
4548 $excerpt = preg_replace( '/&[^;\s]{0,6}$/', '', $excerpt );
4549 if ( $str != $excerpt )
4550 $excerpt = trim( $excerpt ) . $more;
4551 return $excerpt;
4552}
4553
4554/**
4555 * Add a Base url to relative links in passed content.
4556 *
4557 * By default it supports the 'src' and 'href' attributes. However this can be
4558 * changed via the 3rd param.
4559 *
4560 * @since 2.7.0
4561 *
4562 * @global string $_links_add_base
4563 *
4564 * @param string $content String to search for links in.
4565 * @param string $base The base URL to prefix to links.
4566 * @param array $attrs The attributes which should be processed.
4567 * @return string The processed content.
4568 */
4569function links_add_base_url( $content, $base, $attrs = array('src', 'href') ) {
4570 global $_links_add_base;
4571 $_links_add_base = $base;
4572 $attrs = implode('|', (array)$attrs);
4573 return preg_replace_callback( "!($attrs)=(['\"])(.+?)\\2!i", '_links_add_base', $content );
4574}
4575
4576/**
4577 * Callback to add a base url to relative links in passed content.
4578 *
4579 * @since 2.7.0
4580 * @access private
4581 *
4582 * @global string $_links_add_base
4583 *
4584 * @param string $m The matched link.
4585 * @return string The processed link.
4586 */
4587function _links_add_base( $m ) {
4588 global $_links_add_base;
4589 //1 = attribute name 2 = quotation mark 3 = URL
4590 return $m[1] . '=' . $m[2] .
4591 ( preg_match( '#^(\w{1,20}):#', $m[3], $protocol ) && in_array( $protocol[1], wp_allowed_protocols() ) ?
4592 $m[3] :
4593 WP_Http::make_absolute_url( $m[3], $_links_add_base )
4594 )
4595 . $m[2];
4596}
4597
4598/**
4599 * Adds a Target attribute to all links in passed content.
4600 *
4601 * This function by default only applies to `<a>` tags, however this can be
4602 * modified by the 3rd param.
4603 *
4604 * *NOTE:* Any current target attributed will be stripped and replaced.
4605 *
4606 * @since 2.7.0
4607 *
4608 * @global string $_links_add_target
4609 *
4610 * @param string $content String to search for links in.
4611 * @param string $target The Target to add to the links.
4612 * @param array $tags An array of tags to apply to.
4613 * @return string The processed content.
4614 */
4615function links_add_target( $content, $target = '_blank', $tags = array('a') ) {
4616 global $_links_add_target;
4617 $_links_add_target = $target;
4618 $tags = implode('|', (array)$tags);
4619 return preg_replace_callback( "!<($tags)([^>]*)>!i", '_links_add_target', $content );
4620}
4621
4622/**
4623 * Callback to add a target attribute to all links in passed content.
4624 *
4625 * @since 2.7.0
4626 * @access private
4627 *
4628 * @global string $_links_add_target
4629 *
4630 * @param string $m The matched link.
4631 * @return string The processed link.
4632 */
4633function _links_add_target( $m ) {
4634 global $_links_add_target;
4635 $tag = $m[1];
4636 $link = preg_replace('|( target=([\'"])(.*?)\2)|i', '', $m[2]);
4637 return '<' . $tag . $link . ' target="' . esc_attr( $_links_add_target ) . '">';
4638}
4639
4640/**
4641 * Normalize EOL characters and strip duplicate whitespace.
4642 *
4643 * @since 2.7.0
4644 *
4645 * @param string $str The string to normalize.
4646 * @return string The normalized string.
4647 */
4648function normalize_whitespace( $str ) {
4649 $str = trim( $str );
4650 $str = str_replace( "\r", "\n", $str );
4651 $str = preg_replace( array( '/\n+/', '/[ \t]+/' ), array( "\n", ' ' ), $str );
4652 return $str;
4653}
4654
4655/**
4656 * Properly strip all HTML tags including script and style
4657 *
4658 * This differs from strip_tags() because it removes the contents of
4659 * the `<script>` and `<style>` tags. E.g. `strip_tags( '<script>something</script>' )`
4660 * will return 'something'. wp_strip_all_tags will return ''
4661 *
4662 * @since 2.9.0
4663 *
4664 * @param string $string String containing HTML tags
4665 * @param bool $remove_breaks Optional. Whether to remove left over line breaks and white space chars
4666 * @return string The processed string.
4667 */
4668function wp_strip_all_tags($string, $remove_breaks = false) {
4669 $string = preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $string );
4670 $string = strip_tags($string);
4671
4672 if ( $remove_breaks )
4673 $string = preg_replace('/[\r\n\t ]+/', ' ', $string);
4674
4675 return trim( $string );
4676}
4677
4678/**
4679 * Sanitizes a string from user input or from the database.
4680 *
4681 * - Checks for invalid UTF-8,
4682 * - Converts single `<` characters to entities
4683 * - Strips all tags
4684 * - Removes line breaks, tabs, and extra whitespace
4685 * - Strips octets
4686 *
4687 * @since 2.9.0
4688 *
4689 * @see sanitize_textarea_field()
4690 * @see wp_check_invalid_utf8()
4691 * @see wp_strip_all_tags()
4692 *
4693 * @param string $str String to sanitize.
4694 * @return string Sanitized string.
4695 */
4696function sanitize_text_field( $str ) {
4697 $filtered = _sanitize_text_fields( $str, false );
4698
4699 /**
4700 * Filters a sanitized text field string.
4701 *
4702 * @since 2.9.0
4703 *
4704 * @param string $filtered The sanitized string.
4705 * @param string $str The string prior to being sanitized.
4706 */
4707 return apply_filters( 'sanitize_text_field', $filtered, $str );
4708}
4709
4710/**
4711 * Sanitizes a multiline string from user input or from the database.
4712 *
4713 * The function is like sanitize_text_field(), but preserves
4714 * new lines (\n) and other whitespace, which are legitimate
4715 * input in textarea elements.
4716 *
4717 * @see sanitize_text_field()
4718 *
4719 * @since 4.7.0
4720 *
4721 * @param string $str String to sanitize.
4722 * @return string Sanitized string.
4723 */
4724function sanitize_textarea_field( $str ) {
4725 $filtered = _sanitize_text_fields( $str, true );
4726
4727 /**
4728 * Filters a sanitized textarea field string.
4729 *
4730 * @since 4.7.0
4731 *
4732 * @param string $filtered The sanitized string.
4733 * @param string $str The string prior to being sanitized.
4734 */
4735 return apply_filters( 'sanitize_textarea_field', $filtered, $str );
4736}
4737
4738/**
4739 * Internal helper function to sanitize a string from user input or from the db
4740 *
4741 * @since 4.7.0
4742 * @access private
4743 *
4744 * @param string $str String to sanitize.
4745 * @param bool $keep_newlines optional Whether to keep newlines. Default: false.
4746 * @return string Sanitized string.
4747 */
4748function _sanitize_text_fields( $str, $keep_newlines = false ) {
4749 $filtered = wp_check_invalid_utf8( $str );
4750
4751 if ( strpos($filtered, '<') !== false ) {
4752 $filtered = wp_pre_kses_less_than( $filtered );
4753 // This will strip extra whitespace for us.
4754 $filtered = wp_strip_all_tags( $filtered, false );
4755
4756 // Use html entities in a special case to make sure no later
4757 // newline stripping stage could lead to a functional tag
4758 $filtered = str_replace("<\n", "<\n", $filtered);
4759 }
4760
4761 if ( ! $keep_newlines ) {
4762 $filtered = preg_replace( '/[\r\n\t ]+/', ' ', $filtered );
4763 }
4764 $filtered = trim( $filtered );
4765
4766 $found = false;
4767 while ( preg_match('/%[a-f0-9]{2}/i', $filtered, $match) ) {
4768 $filtered = str_replace($match[0], '', $filtered);
4769 $found = true;
4770 }
4771
4772 if ( $found ) {
4773 // Strip out the whitespace that may now exist after removing the octets.
4774 $filtered = trim( preg_replace('/ +/', ' ', $filtered) );
4775 }
4776
4777 return $filtered;
4778}
4779
4780/**
4781 * i18n friendly version of basename()
4782 *
4783 * @since 3.1.0
4784 *
4785 * @param string $path A path.
4786 * @param string $suffix If the filename ends in suffix this will also be cut off.
4787 * @return string
4788 */
4789function wp_basename( $path, $suffix = '' ) {
4790 return urldecode( basename( str_replace( array( '%2F', '%5C' ), '/', urlencode( $path ) ), $suffix ) );
4791}
4792
4793/**
4794 * Forever eliminate "Wordpress" from the planet (or at least the little bit we can influence).
4795 *
4796 * Violating our coding standards for a good function name.
4797 *
4798 * @since 3.0.0
4799 *
4800 * @staticvar string|false $dblq
4801 *
4802 * @param string $text The text to be modified.
4803 * @return string The modified text.
4804 */
4805function capital_P_dangit( $text ) {
4806 // Simple replacement for titles
4807 $current_filter = current_filter();
4808 if ( 'the_title' === $current_filter || 'wp_title' === $current_filter )
4809 return str_replace( 'Wordpress', 'WordPress', $text );
4810 // Still here? Use the more judicious replacement
4811 static $dblq = false;
4812 if ( false === $dblq ) {
4813 $dblq = _x( '“', 'opening curly double quote' );
4814 }
4815 return str_replace(
4816 array( ' Wordpress', '‘Wordpress', $dblq . 'Wordpress', '>Wordpress', '(Wordpress' ),
4817 array( ' WordPress', '‘WordPress', $dblq . 'WordPress', '>WordPress', '(WordPress' ),
4818 $text );
4819}
4820
4821/**
4822 * Sanitize a mime type
4823 *
4824 * @since 3.1.3
4825 *
4826 * @param string $mime_type Mime type
4827 * @return string Sanitized mime type
4828 */
4829function sanitize_mime_type( $mime_type ) {
4830 $sani_mime_type = preg_replace( '/[^-+*.a-zA-Z0-9\/]/', '', $mime_type );
4831 /**
4832 * Filters a mime type following sanitization.
4833 *
4834 * @since 3.1.3
4835 *
4836 * @param string $sani_mime_type The sanitized mime type.
4837 * @param string $mime_type The mime type prior to sanitization.
4838 */
4839 return apply_filters( 'sanitize_mime_type', $sani_mime_type, $mime_type );
4840}
4841
4842/**
4843 * Sanitize space or carriage return separated URLs that are used to send trackbacks.
4844 *
4845 * @since 3.4.0
4846 *
4847 * @param string $to_ping Space or carriage return separated URLs
4848 * @return string URLs starting with the http or https protocol, separated by a carriage return.
4849 */
4850function sanitize_trackback_urls( $to_ping ) {
4851 $urls_to_ping = preg_split( '/[\r\n\t ]/', trim( $to_ping ), -1, PREG_SPLIT_NO_EMPTY );
4852 foreach ( $urls_to_ping as $k => $url ) {
4853 if ( !preg_match( '#^https?://.#i', $url ) )
4854 unset( $urls_to_ping[$k] );
4855 }
4856 $urls_to_ping = array_map( 'esc_url_raw', $urls_to_ping );
4857 $urls_to_ping = implode( "\n", $urls_to_ping );
4858 /**
4859 * Filters a list of trackback URLs following sanitization.
4860 *
4861 * The string returned here consists of a space or carriage return-delimited list
4862 * of trackback URLs.
4863 *
4864 * @since 3.4.0
4865 *
4866 * @param string $urls_to_ping Sanitized space or carriage return separated URLs.
4867 * @param string $to_ping Space or carriage return separated URLs before sanitization.
4868 */
4869 return apply_filters( 'sanitize_trackback_urls', $urls_to_ping, $to_ping );
4870}
4871
4872/**
4873 * Add slashes to a string or array of strings.
4874 *
4875 * This should be used when preparing data for core API that expects slashed data.
4876 * This should not be used to escape data going directly into an SQL query.
4877 *
4878 * @since 3.6.0
4879 *
4880 * @param string|array $value String or array of strings to slash.
4881 * @return string|array Slashed $value
4882 */
4883function wp_slash( $value ) {
4884 if ( is_array( $value ) ) {
4885 foreach ( $value as $k => $v ) {
4886 if ( is_array( $v ) ) {
4887 $value[$k] = wp_slash( $v );
4888 } else {
4889 $value[$k] = addslashes( $v );
4890 }
4891 }
4892 } else {
4893 $value = addslashes( $value );
4894 }
4895
4896 return $value;
4897}
4898
4899/**
4900 * Remove slashes from a string or array of strings.
4901 *
4902 * This should be used to remove slashes from data passed to core API that
4903 * expects data to be unslashed.
4904 *
4905 * @since 3.6.0
4906 *
4907 * @param string|array $value String or array of strings to unslash.
4908 * @return string|array Unslashed $value
4909 */
4910function wp_unslash( $value ) {
4911 return stripslashes_deep( $value );
4912}
4913
4914/**
4915 * Extract and return the first URL from passed content.
4916 *
4917 * @since 3.6.0
4918 *
4919 * @param string $content A string which might contain a URL.
4920 * @return string|false The found URL.
4921 */
4922function get_url_in_content( $content ) {
4923 if ( empty( $content ) ) {
4924 return false;
4925 }
4926
4927 if ( preg_match( '/<a\s[^>]*?href=([\'"])(.+?)\1/is', $content, $matches ) ) {
4928 return esc_url_raw( $matches[2] );
4929 }
4930
4931 return false;
4932}
4933
4934/**
4935 * Returns the regexp for common whitespace characters.
4936 *
4937 * By default, spaces include new lines, tabs, nbsp entities, and the UTF-8 nbsp.
4938 * This is designed to replace the PCRE \s sequence. In ticket #22692, that
4939 * sequence was found to be unreliable due to random inclusion of the A0 byte.
4940 *
4941 * @since 4.0.0
4942 *
4943 * @staticvar string $spaces
4944 *
4945 * @return string The spaces regexp.
4946 */
4947function wp_spaces_regexp() {
4948 static $spaces = '';
4949
4950 if ( empty( $spaces ) ) {
4951 /**
4952 * Filters the regexp for common whitespace characters.
4953 *
4954 * This string is substituted for the \s sequence as needed in regular
4955 * expressions. For websites not written in English, different characters
4956 * may represent whitespace. For websites not encoded in UTF-8, the 0xC2 0xA0
4957 * sequence may not be in use.
4958 *
4959 * @since 4.0.0
4960 *
4961 * @param string $spaces Regexp pattern for matching common whitespace characters.
4962 */
4963 $spaces = apply_filters( 'wp_spaces_regexp', '[\r\n\t ]|\xC2\xA0| ' );
4964 }
4965
4966 return $spaces;
4967}
4968
4969/**
4970 * Print the important emoji-related styles.
4971 *
4972 * @since 4.2.0
4973 *
4974 * @staticvar bool $printed
4975 */
4976function print_emoji_styles() {
4977 static $printed = false;
4978
4979 if ( $printed ) {
4980 return;
4981 }
4982
4983 $printed = true;
4984?>
4985<style type="text/css">
4986img.wp-smiley,
4987img.emoji {
4988 display: inline !important;
4989 border: none !important;
4990 box-shadow: none !important;
4991 height: 1em !important;
4992 width: 1em !important;
4993 margin: 0 .07em !important;
4994 vertical-align: -0.1em !important;
4995 background: none !important;
4996 padding: 0 !important;
4997}
4998</style>
4999<?php
5000}
5001
5002/**
5003 * Print the inline Emoji detection script if it is not already printed.
5004 *
5005 * @since 4.2.0
5006 * @staticvar bool $printed
5007 */
5008function print_emoji_detection_script() {
5009 static $printed = false;
5010
5011 if ( $printed ) {
5012 return;
5013 }
5014
5015 $printed = true;
5016
5017 _print_emoji_detection_script();
5018}
5019
5020/**
5021 * Prints inline Emoji dection script
5022 *
5023 * @ignore
5024 * @since 4.6.0
5025 * @access private
5026 */
5027function _print_emoji_detection_script() {
5028 $settings = array(
5029 /**
5030 * Filters the URL where emoji png images are hosted.
5031 *
5032 * @since 4.2.0
5033 *
5034 * @param string The emoji base URL for png images.
5035 */
5036 'baseUrl' => apply_filters( 'emoji_url', 'https://s.w.org/images/core/emoji/2.3/72x72/' ),
5037
5038 /**
5039 * Filters the extension of the emoji png files.
5040 *
5041 * @since 4.2.0
5042 *
5043 * @param string The emoji extension for png files. Default .png.
5044 */
5045 'ext' => apply_filters( 'emoji_ext', '.png' ),
5046
5047 /**
5048 * Filters the URL where emoji SVG images are hosted.
5049 *
5050 * @since 4.6.0
5051 *
5052 * @param string The emoji base URL for svg images.
5053 */
5054 'svgUrl' => apply_filters( 'emoji_svg_url', 'https://s.w.org/images/core/emoji/2.3/svg/' ),
5055
5056 /**
5057 * Filters the extension of the emoji SVG files.
5058 *
5059 * @since 4.6.0
5060 *
5061 * @param string The emoji extension for svg files. Default .svg.
5062 */
5063 'svgExt' => apply_filters( 'emoji_svg_ext', '.svg' ),
5064 );
5065
5066 $version = 'ver=' . get_bloginfo( 'version' );
5067
5068 if ( SCRIPT_DEBUG ) {
5069 $settings['source'] = array(
5070 /** This filter is documented in wp-includes/class.wp-scripts.php */
5071 'wpemoji' => apply_filters( 'script_loader_src', includes_url( "js/wp-emoji.js?$version" ), 'wpemoji' ),
5072 /** This filter is documented in wp-includes/class.wp-scripts.php */
5073 'twemoji' => apply_filters( 'script_loader_src', includes_url( "js/twemoji.js?$version" ), 'twemoji' ),
5074 );
5075
5076 ?>
5077 <script type="text/javascript">
5078 window._wpemojiSettings = <?php echo wp_json_encode( $settings ); ?>;
5079 <?php readfile( ABSPATH . WPINC . "/js/wp-emoji-loader.js" ); ?>
5080 </script>
5081 <?php
5082 } else {
5083 $settings['source'] = array(
5084 /** This filter is documented in wp-includes/class.wp-scripts.php */
5085 'concatemoji' => apply_filters( 'script_loader_src', includes_url( "js/wp-emoji-release.min.js?$version" ), 'concatemoji' ),
5086 );
5087
5088 /*
5089 * If you're looking at a src version of this file, you'll see an "include"
5090 * statement below. This is used by the `grunt build` process to directly
5091 * include a minified version of wp-emoji-loader.js, instead of using the
5092 * readfile() method from above.
5093 *
5094 * If you're looking at a build version of this file, you'll see a string of
5095 * minified JavaScript. If you need to debug it, please turn on SCRIPT_DEBUG
5096 * and edit wp-emoji-loader.js directly.
5097 */
5098 ?>
5099 <script type="text/javascript">
5100 window._wpemojiSettings = <?php echo wp_json_encode( $settings ); ?>;
5101 !function(a,b,c){function d(a,b){var c=String.fromCharCode;l.clearRect(0,0,k.width,k.height),l.fillText(c.apply(this,a),0,0);var d=k.toDataURL();l.clearRect(0,0,k.width,k.height),l.fillText(c.apply(this,b),0,0);var e=k.toDataURL();return d===e}function e(a){var b;if(!l||!l.fillText)return!1;switch(l.textBaseline="top",l.font="600 32px Arial",a){case"flag":return!(b=d([55356,56826,55356,56819],[55356,56826,8203,55356,56819]))&&(b=d([55356,57332,56128,56423,56128,56418,56128,56421,56128,56430,56128,56423,56128,56447],[55356,57332,8203,56128,56423,8203,56128,56418,8203,56128,56421,8203,56128,56430,8203,56128,56423,8203,56128,56447]),!b);case"emoji":return b=d([55358,56794,8205,9794,65039],[55358,56794,8203,9794,65039]),!b}return!1}function f(a){var c=b.createElement("script");c.src=a,c.defer=c.type="text/javascript",b.getElementsByTagName("head")[0].appendChild(c)}var g,h,i,j,k=b.createElement("canvas"),l=k.getContext&&k.getContext("2d");for(j=Array("flag","emoji"),c.supports={everything:!0,everythingExceptFlag:!0},i=0;i<j.length;i++)c.supports[j[i]]=e(j[i]),c.supports.everything=c.supports.everything&&c.supports[j[i]],"flag"!==j[i]&&(c.supports.everythingExceptFlag=c.supports.everythingExceptFlag&&c.supports[j[i]]);c.supports.everythingExceptFlag=c.supports.everythingExceptFlag&&!c.supports.flag,c.DOMReady=!1,c.readyCallback=function(){c.DOMReady=!0},c.supports.everything||(h=function(){c.readyCallback()},b.addEventListener?(b.addEventListener("DOMContentLoaded",h,!1),a.addEventListener("load",h,!1)):(a.attachEvent("onload",h),b.attachEvent("onreadystatechange",function(){"complete"===b.readyState&&c.readyCallback()})),g=c.source||{},g.concatemoji?f(g.concatemoji):g.wpemoji&&g.twemoji&&(f(g.twemoji),f(g.wpemoji)))}(window,document,window._wpemojiSettings);
5102 </script>
5103 <?php
5104 }
5105}
5106
5107/**
5108 * Convert emoji characters to their equivalent HTML entity.
5109 *
5110 * This allows us to store emoji in a DB using the utf8 character set.
5111 *
5112 * @since 4.2.0
5113 *
5114 * @param string $content The content to encode.
5115 * @return string The encoded content.
5116 */
5117function wp_encode_emoji( $content ) {
5118 $emoji = _wp_emoji_list( 'partials' );
5119
5120 foreach ( $emoji as $emojum ) {
5121 if ( version_compare( phpversion(), '5.4', '<' ) ) {
5122 $emoji_char = html_entity_decode( $emojum, ENT_COMPAT, 'UTF-8' );
5123 } else {
5124 $emoji_char = html_entity_decode( $emojum );
5125 }
5126 if ( false !== strpos( $content, $emoji_char ) ) {
5127 $content = preg_replace( "/$emoji_char/", $emojum, $content );
5128 }
5129 }
5130
5131 return $content;
5132}
5133
5134/**
5135 * Convert emoji to a static img element.
5136 *
5137 * @since 4.2.0
5138 *
5139 * @param string $text The content to encode.
5140 * @return string The encoded content.
5141 */
5142function wp_staticize_emoji( $text ) {
5143 if ( false === strpos( $text, '&#x' ) ) {
5144 if ( ( function_exists( 'mb_check_encoding' ) && mb_check_encoding( $text, 'ASCII' ) ) || ! preg_match( '/[^\x00-\x7F]/', $text ) ) {
5145 // The text doesn't contain anything that might be emoji, so we can return early.
5146 return $text;
5147 } else {
5148 $encoded_text = wp_encode_emoji( $text );
5149 if ( $encoded_text === $text ) {
5150 return $encoded_text;
5151 }
5152
5153 $text = $encoded_text;
5154 }
5155 }
5156
5157 $emoji = _wp_emoji_list( 'entities' );
5158
5159 // Quickly narrow down the list of emoji that might be in the text and need replacing.
5160 $possible_emoji = array();
5161 foreach( $emoji as $emojum ) {
5162 if ( false !== strpos( $text, $emojum ) ) {
5163 if ( version_compare( phpversion(), '5.4', '<' ) ) {
5164 $possible_emoji[ $emojum ] = html_entity_decode( $emojum, ENT_COMPAT, 'UTF-8' );
5165 } else {
5166 $possible_emoji[ $emojum ] = html_entity_decode( $emojum );
5167 }
5168 }
5169 }
5170
5171 if ( ! $possible_emoji ) {
5172 return $text;
5173 }
5174
5175 /** This filter is documented in wp-includes/formatting.php */
5176 $cdn_url = apply_filters( 'emoji_url', 'https://s.w.org/images/core/emoji/2.3/72x72/' );
5177
5178 /** This filter is documented in wp-includes/formatting.php */
5179 $ext = apply_filters( 'emoji_ext', '.png' );
5180
5181 $output = '';
5182 /*
5183 * HTML loop taken from smiley function, which was taken from texturize function.
5184 * It'll never be consolidated.
5185 *
5186 * First, capture the tags as well as in between.
5187 */
5188 $textarr = preg_split( '/(<.*>)/U', $text, -1, PREG_SPLIT_DELIM_CAPTURE );
5189 $stop = count( $textarr );
5190
5191 // Ignore processing of specific tags.
5192 $tags_to_ignore = 'code|pre|style|script|textarea';
5193 $ignore_block_element = '';
5194
5195 for ( $i = 0; $i < $stop; $i++ ) {
5196 $content = $textarr[$i];
5197
5198 // If we're in an ignore block, wait until we find its closing tag.
5199 if ( '' == $ignore_block_element && preg_match( '/^<(' . $tags_to_ignore . ')>/', $content, $matches ) ) {
5200 $ignore_block_element = $matches[1];
5201 }
5202
5203 // If it's not a tag and not in ignore block.
5204 if ( '' == $ignore_block_element && strlen( $content ) > 0 && '<' != $content[0] && false !== strpos( $content, '&#x' ) ) {
5205 foreach ( $possible_emoji as $emojum => $emoji_char ) {
5206 if ( false === strpos( $content, $emojum ) ) {
5207 continue;
5208 }
5209
5210 $file = str_replace( ';&#x', '-', $emojum );
5211 $file = str_replace( array( '&#x', ';'), '', $file );
5212
5213 $entity = sprintf( '<img src="%s" alt="%s" class="wp-smiley" style="height: 1em; max-height: 1em;" />', $cdn_url . $file . $ext, $emoji_char );
5214
5215 $content = str_replace( $emojum, $entity, $content );
5216 }
5217 }
5218
5219 // Did we exit ignore block.
5220 if ( '' != $ignore_block_element && '</' . $ignore_block_element . '>' == $content ) {
5221 $ignore_block_element = '';
5222 }
5223
5224 $output .= $content;
5225 }
5226
5227 // Finally, remove any stray U+FE0F characters
5228 $output = str_replace( '️', '', $output );
5229
5230 return $output;
5231}
5232
5233/**
5234 * Convert emoji in emails into static images.
5235 *
5236 * @since 4.2.0
5237 *
5238 * @param array $mail The email data array.
5239 * @return array The email data array, with emoji in the message staticized.
5240 */
5241function wp_staticize_emoji_for_email( $mail ) {
5242 if ( ! isset( $mail['message'] ) ) {
5243 return $mail;
5244 }
5245
5246 /*
5247 * We can only transform the emoji into images if it's a text/html email.
5248 * To do that, here's a cut down version of the same process that happens
5249 * in wp_mail() - get the Content-Type from the headers, if there is one,
5250 * then pass it through the wp_mail_content_type filter, in case a plugin
5251 * is handling changing the Content-Type.
5252 */
5253 $headers = array();
5254 if ( isset( $mail['headers'] ) ) {
5255 if ( is_array( $mail['headers'] ) ) {
5256 $headers = $mail['headers'];
5257 } else {
5258 $headers = explode( "\n", str_replace( "\r\n", "\n", $mail['headers'] ) );
5259 }
5260 }
5261
5262 foreach ( $headers as $header ) {
5263 if ( strpos($header, ':') === false ) {
5264 continue;
5265 }
5266
5267 // Explode them out.
5268 list( $name, $content ) = explode( ':', trim( $header ), 2 );
5269
5270 // Cleanup crew.
5271 $name = trim( $name );
5272 $content = trim( $content );
5273
5274 if ( 'content-type' === strtolower( $name ) ) {
5275 if ( strpos( $content, ';' ) !== false ) {
5276 list( $type, $charset ) = explode( ';', $content );
5277 $content_type = trim( $type );
5278 } else {
5279 $content_type = trim( $content );
5280 }
5281 break;
5282 }
5283 }
5284
5285 // Set Content-Type if we don't have a content-type from the input headers.
5286 if ( ! isset( $content_type ) ) {
5287 $content_type = 'text/plain';
5288 }
5289
5290 /** This filter is documented in wp-includes/pluggable.php */
5291 $content_type = apply_filters( 'wp_mail_content_type', $content_type );
5292
5293 if ( 'text/html' === $content_type ) {
5294 $mail['message'] = wp_staticize_emoji( $mail['message'] );
5295 }
5296
5297 return $mail;
5298}
5299
5300/**
5301 * Returns a arrays of emoji data.
5302 *
5303 * These arrays automatically built from the regex in twemoji.js - if they need to be updated,
5304 * you should update the regex there, then run the `grunt precommit:emoji` job.
5305 *
5306 * @since 4.9.0
5307 * @access private
5308 *
5309 * @param string $type Optional. Which array type to return. Accepts 'partials' or 'entities', default 'entities'.
5310 * @return array An array to match all emoji that WordPress recognises.
5311 */
5312function _wp_emoji_list( $type = 'entities' ) {
5313 // Do not remove the START/END comments - they're used to find where to insert the arrays.
5314
5315 // START: emoji arrays
5316 $entities = array('👩‍❤️‍💋‍👩','👩‍❤️‍💋‍👨','👨‍❤️‍💋‍👨','🏴󠁧󠁢󠁳󠁣󠁴󠁿','🏴󠁧󠁢󠁷󠁬󠁳󠁿','🏴󠁧󠁢󠁥󠁮󠁧󠁿','👩‍👩‍👧‍👦','👨‍👨‍👦‍👦','👩‍👩‍👦‍👦','👨‍👨‍👧‍👦','👨‍👨‍👧‍👧','👨‍👩‍👧‍👧','👨‍👩‍👦‍👦','👩‍👩‍👧‍👧','👨‍👩‍👧‍👦','👨‍❤️‍👨','👩‍❤️‍👨','👩‍❤️‍👩','👩‍👩‍👦','👩‍👦‍👦','👩‍👧‍👦','👩‍👧‍👧','👨‍👨‍👦','👨‍👩‍👧','👨‍👧‍👧','👨‍👧‍👦','👩‍👩‍👧','👨‍👩‍👦','👨‍👨‍👧','👨‍👦‍👦','🤷🏻‍♂️','🏋🏻‍♂️','🏋🏼‍♀️','🏋🏼‍♂️','🏋🏽‍♀️','🏋🏽‍♂️','🏋🏾‍♀️','🏋🏾‍♂️','🏋🏿‍♀️','🏋🏿‍♂️','🏌🏻‍♀️','🏌🏻‍♂️','🏌🏼‍♀️','🏌🏼‍♂️','🏌🏽‍♀️','🏌🏽‍♂️','🏌🏾‍♀️','🏌🏾‍♂️','🏌🏿‍♀️','🏌🏿‍♂️','💂🏻‍♀️','🏃🏼‍♀️','🏃🏼‍♂️','🧝🏿‍♂️','🧝🏿‍♀️','🧝🏾‍♂️','🧝🏾‍♀️','🧝🏽‍♂️','🧝🏽‍♀️','🧝🏼‍♂️','🧝🏼‍♀️','🧝🏻‍♂️','🧝🏻‍♀️','🧜🏿‍♂️','🧜🏿‍♀️','🧜🏾‍♂️','🧜🏾‍♀️','👨🏻‍⚕️','👨🏻‍⚖️','👨🏻‍✈️','🧜🏽‍♂️','🧜🏽‍♀️','🧜🏼‍♂️','🧜🏼‍♀️','🧜🏻‍♂️','🧜🏻‍♀️','🧛🏿‍♂️','🧛🏿‍♀️','🧛🏾‍♂️','🧛🏾‍♀️','🧛🏽‍♂️','🧛🏽‍♀️','🧛🏼‍♂️','👨🏼‍⚕️','👨🏼‍⚖️','👨🏼‍✈️','🧛🏼‍♀️','🧛🏻‍♂️','🧛🏻‍♀️','🧚🏿‍♂️','🧚🏿‍♀️','🧚🏾‍♂️','🧚🏾‍♀️','🧚🏽‍♂️','🧚🏽‍♀️','🧚🏼‍♂️','🧚🏼‍♀️','🧚🏻‍♂️','🧚🏻‍♀️','👨🏽‍⚕️','👨🏽‍⚖️','👨🏽‍✈️','🧙🏿‍♂️','🧙🏿‍♀️','🧙🏾‍♂️','🧙🏾‍♀️','🧙🏽‍♂️','🧙🏽‍♀️','🧙🏼‍♂️','🧙🏼‍♀️','🧙🏻‍♂️','🧙🏻‍♀️','🧘🏿‍♂️','🧘🏿‍♀️','🧘🏾‍♂️','👨🏾‍⚕️','👨🏾‍⚖️','👨🏾‍✈️','🧘🏾‍♀️','🧘🏽‍♂️','🧘🏽‍♀️','🧘🏼‍♂️','🧘🏼‍♀️','🧘🏻‍♂️','🧘🏻‍♀️','🧗🏿‍♂️','🧗🏿‍♀️','🧗🏾‍♂️','🧗🏾‍♀️','🧗🏽‍♂️','🧗🏽‍♀️','👨🏿‍⚕️','👨🏿‍⚖️','👨🏿‍✈️','🧗🏼‍♂️','🧗🏼‍♀️','🧗🏻‍♂️','🧗🏻‍♀️','🧖🏿‍♂️','🧖🏿‍♀️','🧖🏾‍♂️','🏃🏽‍♀️','🧖🏾‍♀️','🏃🏽‍♂️','🏃🏾‍♀️','🧖🏽‍♂️','🏃🏾‍♂️','🏃🏿‍♀️','🏃🏿‍♂️','🏄🏻‍♀️','🏄🏻‍♂️','🏄🏼‍♀️','🏄🏼‍♂️','🏄🏽‍♀️','🏄🏽‍♂️','🏄🏾‍♀️','🧖🏽‍♀️','🧖🏼‍♂️','🧖🏼‍♀️','🧖🏻‍♂️','🧖🏻‍♀️','🤾🏿‍♂️','🤾🏿‍♀️','🤾🏾‍♂️','🤾🏾‍♀️','🏄🏾‍♂️','🏄🏿‍♀️','🤾🏽‍♂️','🤾🏽‍♀️','🤾🏼‍♂️','🤾🏼‍♀️','🤾🏻‍♂️','🤾🏻‍♀️','🤽🏿‍♂️','🤽🏿‍♀️','🤽🏾‍♂️','🤽🏾‍♀️','🤽🏽‍♂️','🤽🏽‍♀️','🤽🏼‍♂️','👩🏻‍⚕️','👩🏻‍⚖️','👩🏻‍✈️','🤽🏼‍♀️','🤽🏻‍♂️','🤽🏻‍♀️','🤹🏿‍♂️','🤹🏿‍♀️','🤹🏾‍♂️','🤹🏾‍♀️','🤹🏽‍♂️','🤹🏽‍♀️','🤹🏼‍♂️','🤹🏼‍♀️','🤹🏻‍♂️','🤹🏻‍♀️','👩🏼‍⚕️','👩🏼‍⚖️','👩🏼‍✈️','🤸🏿‍♂️','🤸🏿‍♀️','🤸🏾‍♂️','🤸🏾‍♀️','🤸🏽‍♂️','🤸🏽‍♀️','🤸🏼‍♂️','🤸🏼‍♀️','🤸🏻‍♂️','🤸🏻‍♀️','🤷🏿‍♂️','🤷🏿‍♀️','🤷🏾‍♂️','👩🏽‍⚕️','👩🏽‍⚖️','👩🏽‍✈️','🤷🏾‍♀️','🤷🏽‍♂️','🤷🏽‍♀️','🤷🏼‍♂️','🤷🏼‍♀️','🏃🏻‍♂️','🤷🏻‍♀️','🤦🏿‍♂️','🤦🏿‍♀️','🤦🏾‍♂️','🤦🏾‍♀️','🤦🏽‍♂️','🤦🏽‍♀️','👩🏾‍⚕️','👩🏾‍⚖️','👩🏾‍✈️','🤦🏼‍♂️','🤦🏼‍♀️','🤦🏻‍♂️','🤦🏻‍♀️','🚶🏿‍♂️','🚶🏿‍♀️','🚶🏾‍♂️','🚶🏾‍♀️','🚶🏽‍♂️','🚶🏽‍♀️','🚶🏼‍♂️','🚶🏼‍♀️','🚶🏻‍♂️','👩🏿‍⚕️','👩🏿‍⚖️','👩🏿‍✈️','🚶🏻‍♀️','🚵🏿‍♂️','🚵🏿‍♀️','🚵🏾‍♂️','🚵🏾‍♀️','🚵🏽‍♂️','🚵🏽‍♀️','🏄🏿‍♂️','🚵🏼‍♂️','🏊🏻‍♀️','🏊🏻‍♂️','🚵🏼‍♀️','🏊🏼‍♀️','🏊🏼‍♂️','🏊🏽‍♀️','🏊🏽‍♂️','🏊🏾‍♀️','🚵🏻‍♂️','🚵🏻‍♀️','🚴🏿‍♂️','🚴🏿‍♀️','🚴🏾‍♂️','🚴🏾‍♀️','🚴🏽‍♂️','🚴🏽‍♀️','🚴🏼‍♂️','🏊🏾‍♂️','🏊🏿‍♀️','🏊🏿‍♂️','🏋🏻‍♀️','👮🏻‍♀️','👮🏻‍♂️','👮🏼‍♀️','👮🏼‍♂️','👮🏽‍♀️','👮🏽‍♂️','👮🏾‍♀️','👮🏾‍♂️','👮🏿‍♀️','👮🏿‍♂️','🚴🏼‍♀️','🚴🏻‍♂️','🚴🏻‍♀️','🚣🏿‍♂️','👱🏻‍♀️','👱🏻‍♂️','👱🏼‍♀️','👱🏼‍♂️','👱🏽‍♀️','👱🏽‍♂️','👱🏾‍♀️','👱🏾‍♂️','👱🏿‍♀️','👱🏿‍♂️','🚣🏿‍♀️','🚣🏾‍♂️','👳🏻‍♀️','👳🏻‍♂️','👳🏼‍♀️','👳🏼‍♂️','👳🏽‍♀️','👳🏽‍♂️','👳🏾‍♀️','👳🏾‍♂️','👳🏿‍♀️','👳🏿‍♂️','🚣🏾‍♀️','🚣🏽‍♂️','👷🏻‍♀️','👷🏻‍♂️','👷🏼‍♀️','👷🏼‍♂️','👷🏽‍♀️','👷🏽‍♂️','👷🏾‍♀️','👷🏾‍♂️','👷🏿‍♀️','👷🏿‍♂️','🚣🏽‍♀️','🚣🏼‍♂️','💁🏻‍♀️','💁🏻‍♂️','💁🏼‍♀️','💁🏼‍♂️','💁🏽‍♀️','💁🏽‍♂️','💁🏾‍♀️','💁🏾‍♂️','💁🏿‍♀️','💁🏿‍♂️','🚣🏼‍♀️','🚣🏻‍♂️','🏃🏻‍♀️','💂🏻‍♂️','💂🏼‍♀️','💂🏼‍♂️','💂🏽‍♀️','💂🏽‍♂️','💂🏾‍♀️','💂🏾‍♂️','💂🏿‍♀️','💂🏿‍♂️','🚣🏻‍♀️','🙎🏿‍♂️','💆🏻‍♀️','💆🏻‍♂️','💆🏼‍♀️','💆🏼‍♂️','💆🏽‍♀️','💆🏽‍♂️','💆🏾‍♀️','💆🏾‍♂️','💆🏿‍♀️','💆🏿‍♂️','🙎🏿‍♀️','🙎🏾‍♂️','💇🏻‍♀️','💇🏻‍♂️','💇🏼‍♀️','💇🏼‍♂️','💇🏽‍♀️','💇🏽‍♂️','💇🏾‍♀️','💇🏾‍♂️','💇🏿‍♀️','💇🏿‍♂️','🙎🏾‍♀️','🙎🏽‍♂️','🕵🏻‍♀️','🕵🏻‍♂️','🕵🏼‍♀️','🕵🏼‍♂️','🕵🏽‍♀️','🕵🏽‍♂️','🕵🏾‍♀️','🕵🏾‍♂️','🕵🏿‍♀️','🕵🏿‍♂️','🙅🏻‍♀️','🙅🏻‍♂️','🙅🏼‍♀️','🙅🏼‍♂️','🙅🏽‍♀️','🙅🏽‍♂️','🙅🏾‍♀️','🙅🏾‍♂️','🙅🏿‍♀️','🙅🏿‍♂️','🙎🏽‍♀️','🙎🏼‍♂️','🙆🏻‍♀️','🙆🏻‍♂️','🙆🏼‍♀️','🙆🏼‍♂️','🙆🏽‍♀️','🙆🏽‍♂️','🙆🏾‍♀️','🙆🏾‍♂️','🙆🏿‍♀️','🙆🏿‍♂️','🙎🏼‍♀️','🙎🏻‍♂️','🙇🏻‍♀️','🙇🏻‍♂️','🙇🏼‍♀️','🙇🏼‍♂️','🙇🏽‍♀️','🙇🏽‍♂️','🙇🏾‍♀️','🙇🏾‍♂️','🙇🏿‍♀️','🙇🏿‍♂️','🙎🏻‍♀️','🙍🏿‍♂️','🙋🏻‍♀️','🙋🏻‍♂️','🙋🏼‍♀️','🙋🏼‍♂️','🙋🏽‍♀️','🙋🏽‍♂️','🙋🏾‍♀️','🙋🏾‍♂️','🙋🏿‍♀️','🙋🏿‍♂️','🙍🏿‍♀️','🙍🏾‍♂️','🙍🏻‍♀️','🙍🏻‍♂️','🙍🏼‍♀️','🙍🏼‍♂️','🙍🏽‍♀️','🙍🏽‍♂️','🙍🏾‍♀️','🏋️‍♂️','⛹🏾‍♀️','🏌️‍♀️','🕵️‍♀️','🕵️‍♂️','🏌️‍♂️','⛹🏻‍♂️','⛹🏻‍♀️','⛹🏾‍♂️','⛹🏿‍♀️','⛹🏿‍♂️','⛹🏽‍♀️','⛹🏽‍♂️','⛹🏼‍♀️','🏋️‍♀️','⛹🏼‍♂️','⛹️‍♂️','⛹️‍♀️','👨🏼‍🌾','👩🏿‍🚒','👩🏿‍🚀','👩🏿‍🔬','👩🏿‍🔧','👩🏿‍💼','👩🏿‍💻','👩🏿‍🏭','👩🏿‍🏫','👩🏿‍🎨','👩🏿‍🎤','👩🏿‍🎓','👩🏿‍🍳','👩🏿‍🌾','👩🏾‍🚒','👩🏾‍🚀','👩🏾‍🔬','👩🏾‍🔧','👩🏾‍💼','👩🏾‍💻','👩🏾‍🏭','👩🏾‍🏫','👩🏾‍🎨','👩🏾‍🎤','👩🏾‍🎓','👩🏾‍🍳','👩🏾‍🌾','👩🏽‍🚒','👩🏽‍🚀','👩🏽‍🔬','👩🏽‍🔧','👩🏽‍💼','👩🏽‍💻','👩🏽‍🏭','👩🏽‍🏫','👩🏽‍🎨','👩🏽‍🎤','👩🏽‍🎓','👩🏽‍🍳','👩🏽‍🌾','👩🏼‍🚒','👩🏼‍🚀','👩🏼‍🔬','👩🏼‍🔧','👩🏼‍💼','👩🏼‍💻','👩🏼‍🏭','👩🏼‍🏫','👩🏼‍🎨','👩🏼‍🎤','👩🏼‍🎓','👩🏼‍🍳','👩🏼‍🌾','👩🏻‍🚒','👩🏻‍🚀','👩🏻‍🔬','👩🏻‍🔧','👩🏻‍💼','👩🏻‍💻','👩🏻‍🏭','👩🏻‍🏫','👩🏻‍🎨','👩🏻‍🎤','👩🏻‍🎓','👨🏻‍🚒','👩🏻‍🌾','👨🏻‍🌾','👨🏻‍🍳','👨🏻‍🎓','👨🏻‍🎤','👨🏻‍🎨','👨🏻‍🏫','👨🏻‍🏭','👨🏻‍💻','👨🏻‍💼','👨🏻‍🔧','👨🏻‍🔬','👨🏻‍🚀','👨🏿‍🚒','👨🏿‍🚀','👨🏿‍🔬','👨🏿‍🔧','👨🏿‍💼','👨🏿‍💻','👨🏿‍🏭','👨🏿‍🏫','👨🏿‍🎨','👨🏿‍🎤','👨🏿‍🎓','👨🏿‍🍳','👨🏿‍🌾','👨🏾‍🚒','👨🏾‍🚀','👨🏾‍🔬','👨🏾‍🔧','👨🏾‍💼','👨🏾‍💻','👨🏾‍🏭','👨🏾‍🏫','👨🏾‍🎨','👨🏾‍🎤','👨🏾‍🎓','👨🏾‍🍳','👨🏾‍🌾','👨🏽‍🚒','👨🏽‍🚀','👨🏽‍🔬','👨🏽‍🔧','👨🏽‍💼','👨🏽‍💻','👨🏽‍🏭','👨🏽‍🏫','👨🏽‍🎨','👨🏽‍🎤','👨🏽‍🎓','👨🏽‍🍳','👨🏽‍🌾','👨🏼‍🚒','👨🏼‍🚀','👨🏼‍🔬','👨🏼‍🔧','👨🏼‍💼','👨🏼‍💻','👨🏼‍🏭','👨🏼‍🏫','👨🏼‍🎨','👨🏼‍🎤','👨🏼‍🎓','👨🏼‍🍳','👩🏻‍🍳','🏳️‍🌈','👨‍⚖️','👨‍⚕️','🧝‍♀️','🤾‍♀️','🤾‍♂️','💂‍♀️','💁‍♂️','💁‍♀️','👷‍♂️','👷‍♀️','👳‍♂️','🤦‍♀️','🤦‍♂️','👳‍♀️','🧜‍♂️','🧖‍♀️','🧖‍♂️','🧜‍♀️','👱‍♂️','👱‍♀️','👯‍♂️','🚣‍♀️','🚣‍♂️','👯‍♀️','👮‍♂️','👮‍♀️','👩‍✈️','🧗‍♀️','🧗‍♂️','🤷‍♀️','🤷‍♂️','👩‍⚖️','👩‍⚕️','🏃‍♀️','🏃‍♂️','🏄‍♀️','🏄‍♂️','🚴‍♀️','🚴‍♂️','🧘‍♀️','🧘‍♂️','🏊‍♀️','🏊‍♂️','🤸‍♀️','🤸‍♂️','🙋‍♂️','🙇‍♂️','🙍‍♀️','🏴‍☠️','🧟‍♂️','🧟‍♀️','🧙‍♀️','🧙‍♂️','🧞‍♂️','🧞‍♀️','🚵‍♀️','🚵‍♂️','🤹‍♀️','🤹‍♂️','🤼‍♀️','🤼‍♂️','🧝‍♂️','🙍‍♂️','🧚‍♀️','🧚‍♂️','🙇‍♀️','🙆‍♂️','🙆‍♀️','🙅‍♂️','🙅‍♀️','💇‍♂️','💇‍♀️','💆‍♂️','🙋‍♀️','🤽‍♂️','🧛‍♀️','🧛‍♂️','🚶‍♀️','🚶‍♂️','💆‍♀️','💂‍♂️','🙎‍♀️','🙎‍♂️','👨‍✈️','🤽‍♀️','👨‍🌾','👨‍🎓','👨‍🎤','👨‍🎨','👨‍🏫','👨‍🏭','👨‍👦','👨‍👧','👨‍💻','👨‍💼','👨‍🔧','👨‍🔬','👨‍🚀','👁‍🗨','👨‍🚒','👨‍🍳','👩‍🍳','👩‍🎓','👩‍🎤','👩‍🎨','👩‍🏫','👩‍🏭','👩‍👦','👩‍👧','👩‍💻','👩‍💼','👩‍🔧','👩‍🔬','👩‍🚀','👩‍🚒','👩‍🌾','🇧🇲','🇧🇳','👱🏾','🏊🏼','🇧🇴','👱🏿','🇧🇶','🏊🏽','👲🏻','👲🏼','👲🏽','👲🏾','👲🏿','🇧🇷','🇧🇸','👳🏻','🏊🏾','🇧🇹','👳🏼','🇧🇻','🏊🏿','👳🏽','🇧🇼','🇧🇾','👳🏾','🇧🇿','🇨🇦','👳🏿','🏋🏻','🇨🇨','👴🏻','👴🏼','👴🏽','👴🏾','👴🏿','👵🏻','👵🏼','👵🏽','👵🏾','👵🏿','👶🏻','👶🏼','👶🏽','👶🏾','👶🏿','🇨🇩','🏋🏼','👷🏻','🇨🇫','🇨🇬','👷🏼','🏋🏽','🇨🇭','👷🏽','🇨🇮','🏋🏾','👷🏾','🇨🇰','🇨🇱','👷🏿','🏋🏿','🇨🇲','👸🏻','👸🏼','👸🏽','👸🏾','👸🏿','👼🏻','👼🏼','👼🏽','👼🏾','👼🏿','🇨🇳','🇨🇴','💁🏻','🇨🇵','🏌🏻','💁🏼','🇨🇷','🇨🇺','💁🏽','🏌🏼','🇨🇻','💁🏾','🇨🇼','🏌🏽','💁🏿','🇨🇽','🇨🇾','🏌🏾','🇨🇿','💂🏻','🇩🇪','🏌🏿','💂🏼','🇩🇬','🇩🇯','💂🏽','🇩🇰','🇩🇲','💂🏾','🇩🇴','🇩🇿','💂🏿','🇪🇦','🇪🇨','💃🏻','💃🏼','💃🏽','💃🏾','💃🏿','💅🏻','💅🏼','💅🏽','💅🏾','💅🏿','👂🏻','👂🏼','💆🏻','👂🏽','👂🏾','💆🏼','👂🏿','👃🏻','💆🏽','👃🏼','👃🏽','💆🏾','👃🏾','👃🏿','💆🏿','👆🏻','👆🏼','👆🏽','👆🏾','💇🏻','👆🏿','👇🏻','💇🏼','👇🏼','👇🏽','💇🏽','👇🏾','👇🏿','💇🏾','👈🏻','👈🏼','💇🏿','👈🏽','👈🏾','💪🏻','💪🏼','💪🏽','💪🏾','💪🏿','🕴🏻','🕴🏼','🕴🏽','🕴🏾','🕴🏿','👈🏿','👉🏻','🕵🏻','👉🏼','👉🏽','🕵🏼','👉🏾','👉🏿','🕵🏽','👊🏻','👊🏼','🕵🏾','👊🏽','👊🏾','🕵🏿','👊🏿','👋🏻','🕺🏻','🕺🏼','🕺🏽','🕺🏾','🕺🏿','🖐🏻','🖐🏼','🖐🏽','🖐🏾','🖐🏿','🖕🏻','🖕🏼','🖕🏽','🖕🏾','🖕🏿','🖖🏻','🖖🏼','🖖🏽','🖖🏾','🖖🏿','👋🏼','👋🏽','🙅🏻','👋🏾','👋🏿','🙅🏼','👌🏻','👌🏼','🙅🏽','🇦🇨','👌🏾','🙅🏾','👌🏿','👍🏻','🙅🏿','👍🏼','👍🏽','👍🏾','👍🏿','🙆🏻','👎🏻','👎🏼','🙆🏼','👎🏽','👎🏾','🙆🏽','👎🏿','👏🏻','🙆🏾','👏🏼','👏🏽','🙆🏿','👏🏾','👏🏿','👐🏻','👐🏼','🙇🏻','👐🏽','👐🏾','🙇🏼','👐🏿','👦🏻','🙇🏽','👦🏼','👦🏽','🙇🏾','👦🏾','👦🏿','🙇🏿','👧🏻','👧🏼','👧🏽','👧🏾','🙋🏻','👧🏿','🇪🇪','🙋🏼','🇪🇬','🇪🇭','🙋🏽','🇪🇷','🇪🇸','🙋🏾','🇪🇹','🇪🇺','🙋🏿','🇫🇮','🇫🇯','🙌🏻','🙌🏼','🙌🏽','🙌🏾','🙌🏿','🇫🇰','🇫🇲','🙍🏻','🇫🇴','🇫🇷','🙍🏼','🇬🇦','🇬🇧','🙍🏽','🇬🇩','👨🏻','🙍🏾','🇬🇪','🇬🇫','🙍🏿','🇬🇬','🇬🇭','🇬🇮','🇬🇱','🙎🏻','🇬🇲','🇬🇳','🙎🏼','🇬🇵','🇬🇶','🙎🏽','🇬🇷','🇬🇸','🙎🏾','🇬🇹','🇬🇺','🙎🏿','🇬🇼','🇬🇾','🙏🏻','🙏🏼','🙏🏽','🙏🏾','🙏🏿','👨🏼','🇭🇰','🚣🏻','🇭🇲','🇭🇳','🚣🏼','🇭🇷','🇭🇹','🚣🏽','🇭🇺','🇮🇨','🚣🏾','🇮🇩','🇮🇪','🚣🏿','🇮🇱','🇮🇲','🇮🇳','🇮🇴','🚴🏻','🇮🇶','🇮🇷','🚴🏼','🇮🇸','👨🏽','🚴🏽','🇮🇹','🇯🇪','🚴🏾','🇯🇲','🇯🇴','🚴🏿','🇯🇵','🇰🇪','🇰🇬','🇰🇭','🚵🏻','🇰🇮','🇰🇲','🚵🏼','🇰🇳','🇰🇵','🚵🏽','🇰🇷','🇰🇼','🚵🏾','🇰🇾','🇰🇿','🚵🏿','👨🏾','🇱🇦','🇱🇧','🇱🇨','🚶🏻','🇱🇮','🇱🇰','🚶🏼','🇱🇷','🇱🇸','🚶🏽','🇱🇹','🇱🇺','🚶🏾','🇱🇻','🇱🇾','🚶🏿','🇲🇦','🇲🇨','🛀🏻','🛀🏼','🛀🏽','🛀🏾','🛀🏿','🛌🏻','🛌🏼','🛌🏽','🛌🏾','🛌🏿','🤘🏻','🤘🏼','🤘🏽','🤘🏾','🤘🏿','🤙🏻','🤙🏼','🤙🏽','🤙🏾','🤙🏿','🤚🏻','🤚🏼','🤚🏽','🤚🏾','🤚🏿','🤛🏻','🤛🏼','🤛🏽','🤛🏾','🤛🏿','🤜🏻','🤜🏼','🤜🏽','🤜🏾','🤜🏿','🤞🏻','🤞🏼','🤞🏽','🤞🏾','🤞🏿','🤟🏻','🤟🏼','🤟🏽','🤟🏾','🤟🏿','🇲🇩','🇲🇪','🤦🏻','🇲🇫','👨🏿','🤦🏼','🇲🇬','🇲🇭','🤦🏽','🇲🇰','🇲🇱','🤦🏾','🇲🇲','🇲🇳','🤦🏿','🇲🇴','🇲🇵','🤰🏻','🤰🏼','🤰🏽','🤰🏾','🤰🏿','🤱🏻','🤱🏼','🤱🏽','🤱🏾','🤱🏿','🤲🏻','🤲🏼','🤲🏽','🤲🏾','🤲🏿','🤳🏻','🤳🏼','🤳🏽','🤳🏾','🤳🏿','🤴🏻','🤴🏼','🤴🏽','🤴🏾','🤴🏿','🤵🏻','🤵🏼','🤵🏽','🤵🏾','🤵🏿','🤶🏻','🤶🏼','🤶🏽','🤶🏾','🤶🏿','🇲🇶','🇲🇷','🤷🏻','🇲🇸','🇲🇹','🤷🏼','🇲🇺','🇲🇻','🤷🏽','🇲🇼','🇲🇽','🤷🏾','🇲🇾','🇲🇿','🤷🏿','🇳🇦','🇳🇨','🇳🇪','🇳🇫','🤸🏻','🇳🇬','🇳🇮','🤸🏼','🇳🇱','🇳🇴','🤸🏽','🇳🇵','🇳🇷','🤸🏾','🇳🇺','🇳🇿','🤸🏿','🇴🇲','🇵🇦','🇵🇪','🇵🇫','🤹🏻','🇵🇬','🇵🇭','🤹🏼','🇵🇰','🇵🇱','🤹🏽','🇵🇲','🇵🇳','🤹🏾','🇵🇷','🇵🇸','🤹🏿','🇵🇹','🇵🇼','🇵🇾','🇶🇦','🇷🇪','🇷🇴','🤽🏻','🇷🇸','👩🏻','🤽🏼','🇷🇺','🇷🇼','🤽🏽','🇸🇦','🇸🇧','🤽🏾','🇸🇨','🇸🇩','🤽🏿','🇸🇪','🇸🇬','🇦🇩','🇸🇮','🤾🏻','🇸🇯','🇸🇰','🤾🏼','🇸🇱','🇸🇲','🤾🏽','🇸🇳','🇸🇴','🤾🏾','👩🏼','🇸🇷','🤾🏿','🇸🇸','🇸🇹','🧑🏻','🧑🏼','🧑🏽','🧑🏾','🧑🏿','🧒🏻','🧒🏼','🧒🏽','🧒🏾','🧒🏿','🧓🏻','🧓🏼','🧓🏽','🧓🏾','🧓🏿','🧔🏻','🧔🏼','🧔🏽','🧔🏾','🧔🏿','🧕🏻','🧕🏼','🧕🏽','🧕🏾','🧕🏿','🇸🇻','🇸🇽','🧖🏻','🇸🇾','🇸🇿','🧖🏼','🇹🇦','🇹🇨','🧖🏽','🇹🇩','🇹🇫','🧖🏾','🇹🇬','🇹🇭','🧖🏿','🇹🇯','🇹🇰','🇹🇱','👩🏽','🧗🏻','🇹🇲','🇹🇳','🧗🏼','🇹🇴','🇹🇷','🧗🏽','🇹🇹','🇹🇻','🧗🏾','🇹🇼','🇹🇿','🧗🏿','🇺🇦','🇺🇬','🇺🇲','🇺🇳','🧘🏻','🇺🇸','🇺🇾','🧘🏼','🇺🇿','🇻🇦','🧘🏽','👩🏾','🇻🇨','🧘🏾','🇻🇪','🇻🇬','🧘🏿','🇻🇮','🇻🇳','🇻🇺','🇼🇫','🧙🏻','🇼🇸','🇽🇰','🧙🏼','🇾🇪','🇾🇹','🧙🏽','🇿🇦','🇿🇲','🧙🏾','🇿🇼','🎅🏻','🧙🏿','🎅🏼','👩🏿','🎅🏽','🎅🏾','🧚🏻','🎅🏿','🏂🏻','🧚🏼','🏂🏼','🏂🏽','🧚🏽','🏂🏾','🏂🏿','🧚🏾','👌🏽','🇦🇪','🧚🏿','🏃🏻','🇦🇫','🇦🇬','🏃🏼','🧛🏻','🇦🇮','🇦🇱','🧛🏼','🏃🏽','🇦🇲','🧛🏽','🇦🇴','🏃🏾','🧛🏾','🇦🇶','🇦🇷','🧛🏿','🏃🏿','🇦🇸','🇦🇹','🇦🇺','🧜🏻','🇦🇼','🏄🏻','🧜🏼','🇦🇽','🇦🇿','🧜🏽','🏄🏼','🇧🇦','🧜🏾','👮🏻','🇧🇧','🧜🏿','🏄🏽','👮🏼','🇧🇩','🇧🇪','🧝🏻','👮🏽','🏄🏾','🧝🏼','🇧🇫','👮🏾','🧝🏽','🇧🇬','🏄🏿','🧝🏾','👮🏿','🇧🇭','🧝🏿','🇧🇮','🏇🏻','🏇🏼','👰🏻','👰🏼','👰🏽','👰🏾','👰🏿','🏇🏽','🏇🏾','👱🏻','🏇🏿','🇧🇯','👱🏼','🇧🇱','🏊🏻','👱🏽','🇸🇭','✍🏿','⛹🏻','✍🏾','✍🏽','✍🏼','✍🏻','✌🏿','✌🏾','✌🏽','✌🏼','✌🏻','✋🏿','✋🏾','✋🏽','✋🏼','✋🏻','✊🏿','✊🏾','✊🏽','✊🏼','✊🏻','⛷🏽','⛷🏾','⛹🏿','☝🏿','☝🏾','⛹🏾','☝🏽','☝🏼','⛹🏽','☝🏻','⛷🏿','⛹🏼','⛷🏻','⛷🏼','4⃣','#⃣','0⃣','1⃣','2⃣','3⃣','*⃣','5⃣','6⃣','7⃣','8⃣','9⃣','🇳','🕔','🕕','🕖','🕗','🕘','🕙','🕚','🕛','🕜','🕝','🕞','🕟','🕠','🕡','🕢','🕣','🕤','🕥','🕦','🕧','🕯','🕰','🕳','🎇','🎈','🎉','🎊','🎋','🕴','🎌','🎍','🎎','🎏','🎐','🎑','🎒','🎓','🎖','🎗','🎙','🎚','🎛','🎞','🎟','🎠','🎡','🕵','🕶','🕷','🕸','🕹','🎢','🎣','🎤','🎥','🎦','🕺','🖇','🖊','🖋','🖌','🖍','🎧','🎨','🎩','🎪','🎫','🖐','🎬','🎭','🎮','🎯','🎰','🖕','🎱','🎲','🎳','🎴','🎵','🖖','🖤','🖥','🖨','🖱','🖲','🖼','🗂','🗃','🗄','🗑','🗒','🗓','🗜','🗝','🗞','🗡','🗣','🗨','🗯','🗳','🗺','🗻','🗼','🗽','🗾','🗿','😀','😁','😂','😃','😄','😅','😆','😇','😈','😉','😊','😋','😌','😍','😎','😏','😐','😑','😒','😓','😔','😕','😖','😗','😘','😙','😚','😛','😜','😝','😞','😟','😠','😡','😢','😣','😤','😥','😦','😧','😨','😩','😪','😫','😬','😭','😮','😯','😰','😱','😲','😳','😴','😵','😶','😷','😸','😹','😺','😻','😼','😽','😾','😿','🙀','🙁','🙂','🙃','🙄','🎶','🎷','🎸','🎹','🎺','🎻','🎼','🎽','🎾','🎿','🏀','🏁','🇧','🇮','🇪','🇷','🇱','🙅','🏂','🆎','🆑','🇨','🇹','🇯','🆒','🇬','🆓','🃏','🆔','🇴','🇺','🇫','🆕','🆖','🆗','🙆','🇭','🏃','🆘','🇩','🇻','🇰','🆙','🇼','🆚','🇽','🇸','🀄','🇾','🇦','🅰','🅱','🇿','🙇','🙈','🙉','🙊','🈁','🈂','🏄','🏅','🏆','🈚','🈯','🈲','🈳','🈴','🏇','👨','🏈','🏉','🈵','🈶','🈷','🙋','🈸','🈹','🈺','🉐','🉑','🙌','🌀','🌁','🌂','🌃','🌄','🌅','🌆','🌇','🌈','🏊','🌉','🌊','🌋','🌌','🌍','🌎','🌏','🙍','🌐','🌑','🌒','🌓','🌔','🌕','🌖','🌗','🌘','🌙','🏋','🌚','🌛','🌜','🌝','🌞','🌟','🙎','🌠','🌡','🌤','🌥','🌦','🙏','🚀','🚁','🚂','🚃','🚄','🚅','🚆','🚇','🚈','🚉','🚊','🚋','🚌','🚍','🚎','🚏','🚐','🚑','🚒','🚓','🚔','🚕','🚖','🚗','🚘','🚙','🚚','🚛','🚜','🚝','🚞','🚟','🚠','🚡','🚢','🌧','🌨','🌩','🌪','🌫','🌬','🏌','🏍','🏎','🏏','🏐','🏑','🏒','🏓','🏔','🏕','🏖','🚣','🚤','🚥','🚦','🚧','🚨','🚩','🚪','🚫','🚬','🚭','🚮','🚯','🚰','🚱','🚲','🚳','🏗','🏘','🏙','🏚','🏛','🏜','🏝','🏞','🏟','🏠','🏡','🏢','🏣','🏤','🏥','🏦','🏧','🚴','🏨','🏩','🏪','🏫','🏬','🏭','🏮','🏯','🏰','🌭','🏳','🌮','🌯','🌰','🌱','🏴','🏵','🚵','🏷','🏸','🏹','🏺','🏻','🏼','🏽','🏾','🏿','🐀','🐁','🐂','🐃','🐄','🐅','👩','👪','🚶','🚷','🚸','🚹','🚺','🚻','🚼','🚽','🚾','🚿','👫','👬','👭','🐆','🐇','🛀','🛁','🛂','🛃','🛄','🛅','🛋','🐈','🐉','🐊','🐋','🐌','🛌','🛍','🛎','🛏','🛐','🛑','🛒','🛠','🛡','🛢','🛣','🛤','🛥','🛩','🛫','🛬','🛰','🛳','🛴','🛵','🛶','🛷','🛸','🤐','🤑','🤒','🤓','🤔','🤕','🤖','🤗','🐍','🐎','🐏','🐐','🐑','🤘','🐒','🐓','🐔','🐕','🐖','🤙','👮','🐗','🐘','👯','🐙','🤚','🐚','🐛','🐜','🐝','👰','🤛','🐞','🐟','🐠','🐡','🐢','🤜','🤝','🐣','🐤','🐥','🐦','🐧','🤞','🐨','🐩','🐪','🐫','🐬','🤟','🤠','🤡','🤢','🤣','🤤','🤥','🐭','🐮','👱','🐯','🐰','🐱','🐲','🐳','👲','🐴','🐵','🐶','🐷','🐸','🐹','🐺','🐻','🤦','🤧','🤨','🤩','🤪','🤫','🤬','🤭','🤮','🤯','🐼','🐽','🐾','🐿','👀','🤰','🌲','👁','🌳','🌴','👳','🤱','🌵','🌶','🌷','👂','🌸','🤲','👴','🌹','🌺','🌻','🌼','🤳','👃','👵','👄','👅','🌽','🤴','🌾','🌿','👶','🍀','🍁','🤵','👆','🍂','🍃','🍄','🍅','🤶','🍆','👇','🍇','🍈','🍉','🍊','🍋','👈','🍌','🍍','👷','🍎','🍏','🍐','👉','🍑','👸','🤷','👹','👺','👻','🍒','🍓','🍔','🍕','👊','👼','👽','👾','👿','💀','🍖','🍗','🍘','🍙','🤸','🍚','👋','🍛','🍜','🍝','🍞','🍟','👌','🍠','🍡','🍢','🍣','🍤','💁','👍','🍥','🍦','🤹','🤺','🍧','🍨','🤼','🍩','👎','🍪','🍫','🍬','🍭','🍮','👏','🍯','🍰','🍱','🍲','💂','🍳','👐','👑','👒','🤽','👓','💃','💄','👔','👕','👖','👗','👘','💅','👙','👚','👛','👜','👝','👞','👟','👠','🤾','🥀','🥁','🥂','🥃','🥄','🥅','🥇','🥈','🥉','🥊','🥋','🥌','🥐','🥑','🥒','🥓','🥔','🥕','🥖','🥗','🥘','🥙','🥚','🥛','🥜','🥝','🥞','🥟','🥠','🥡','🥢','🥣','🥤','🥥','🥦','🥧','🥨','🥩','🥪','🥫','🦀','🦁','🦂','🦃','🦄','🦅','🦆','🦇','🦈','🦉','🦊','🦋','🦌','🦍','🦎','🦏','🦐','🦑','🦒','🦓','🦔','🦕','🦖','🦗','🧀','🧐','👡','👢','👣','👤','👥','🧑','🍴','🍵','🍶','🍷','💆','🧒','🍸','👦','🍹','🍺','🍻','🧓','🍼','🍽','👧','🍾','🍿','🧔','🎀','🎁','🎂','🎃','🎄','🧕','🇵','🅾','💇','💈','💉','💊','💋','💌','💍','💎','💏','💐','💑','💒','💓','💔','💕','🧖','💖','💗','💘','💙','💚','💛','💜','💝','💞','💟','💠','💡','💢','💣','💤','💥','💦','🧗','💧','💨','💩','🇶','🇲','🅿','🎅','🎆','💪','💫','💬','💭','💮','💯','💰','💱','💲','🧘','💳','💴','💵','💶','💷','💸','💹','💺','💻','💼','💽','💾','💿','📀','📁','📂','📃','🧙','📄','📅','📆','📇','📈','📉','📊','📋','📌','📍','📎','📏','📐','📑','📒','📓','📔','🧚','📕','📖','📗','📘','📙','📚','📛','📜','📝','📞','📟','📠','📡','📢','📣','📤','📥','🧛','📦','📧','📨','📩','📪','📫','📬','📭','📮','📯','📰','📱','📲','📳','📴','📵','📶','🧜','📷','📸','📹','📺','📻','📼','📽','📿','🔀','🔁','🔂','🔃','🔄','🔅','🔆','🔇','🔈','🧝','🔉','🔊','🧞','🔋','🔌','🧟','🧠','🧡','🧢','🧣','🧤','🧥','🧦','🔍','🔎','🔏','🔐','🔑','🔒','🔓','🔔','🔕','🔖','🔗','🔘','🔙','🔚','🔛','🔜','🔝','🔞','🔟','🔠','🔡','🔢','🔣','🔤','🔥','🔦','🔧','🔨','🔩','🔪','🔫','🔬','🔭','🔮','🔯','🔰','🔱','🔲','🔳','🔴','🔵','🔶','🔷','🔸','🔹','🔺','🔻','🔼','🔽','🕉','🕊','🕋','🕌','🕍','🕎','🕐','🕑','🕒','🕓','▪','☦','☮','☯','☸','☹','☺','♀','♂','♈','♉','♊','♋','♌','♍','♎','♏','♐','♑','♒','♓','♠','♣','♥','♦','♨','♻','♿','⚒','⚓','⚔','⚕','⚖','⚗','⚙','⚛','⚜','⚠','⚡','⚪','⚫','⚰','⚱','⚽','⚾','⛄','⛅','⛈','⛎','⛏','⛑','⛓','⛔','⛩','⛪','⛰','⛱','⛲','⛳','⛴','⛵','☣','☢','☠','☝','☘','⛷','⛸','☕','☔','☑','☎','☄','☃','☂','☁','☀','◾','◽','◼','◻','◀','▶','▫','☪','⛹','⛺','⛽','✂','✅','✈','✉','Ⓜ','⏺','⏹','⏸','⏳','✊','⏲','⏱','⏰','⏯','⏮','✋','⏭','⏬','⏫','⏪','⏩','✌','⏏','⌨','⌛','⌚','↪','✍','✏','✒','✔','✖','✝','✡','✨','✳','✴','❄','❇','❌','❎','❓','❔','❕','❗','❣','❤','➕','➖','➗','➡','➰','➿','⤴','⤵','↩','⬅','⬆','⬇','⬛','⬜','⭐','⭕','↙','〰','〽','↘','↗','㊗','㊙','↖','↕','↔','ℹ','™','⁉','‼','');
5317 $partials = array('🀄','🃏','🅰','🅱','🅾','🅿','🆎','🆑','🆒','🆓','🆔','🆕','🆖','🆗','🆘','🆙','🆚','🇦','🇨','🇩','🇪','🇫','🇬','🇮','🇱','🇲','🇴','🇶','🇷','🇸','🇹','🇺','🇼','🇽','🇿','🇧','🇭','🇯','🇳','🇻','🇾','🇰','🇵','🈁','🈂','🈚','🈯','🈲','🈳','🈴','🈵','🈶','🈷','🈸','🈹','🈺','🉐','🉑','🌀','🌁','🌂','🌃','🌄','🌅','🌆','🌇','🌈','🌉','🌊','🌋','🌌','🌍','🌎','🌏','🌐','🌑','🌒','🌓','🌔','🌕','🌖','🌗','🌘','🌙','🌚','🌛','🌜','🌝','🌞','🌟','🌠','🌡','🌤','🌥','🌦','🌧','🌨','🌩','🌪','🌫','🌬','🌭','🌮','🌯','🌰','🌱','🌲','🌳','🌴','🌵','🌶','🌷','🌸','🌹','🌺','🌻','🌼','🌽','🌾','🌿','🍀','🍁','🍂','🍃','🍄','🍅','🍆','🍇','🍈','🍉','🍊','🍋','🍌','🍍','🍎','🍏','🍐','🍑','🍒','🍓','🍔','🍕','🍖','🍗','🍘','🍙','🍚','🍛','🍜','🍝','🍞','🍟','🍠','🍡','🍢','🍣','🍤','🍥','🍦','🍧','🍨','🍩','🍪','🍫','🍬','🍭','🍮','🍯','🍰','🍱','🍲','🍳','🍴','🍵','🍶','🍷','🍸','🍹','🍺','🍻','🍼','🍽','🍾','🍿','🎀','🎁','🎂','🎃','🎄','🎅','🏻','🏼','🏽','🏾','🏿','🎆','🎇','🎈','🎉','🎊','🎋','🎌','🎍','🎎','🎏','🎐','🎑','🎒','🎓','🎖','🎗','🎙','🎚','🎛','🎞','🎟','🎠','🎡','🎢','🎣','🎤','🎥','🎦','🎧','🎨','🎩','🎪','🎫','🎬','🎭','🎮','🎯','🎰','🎱','🎲','🎳','🎴','🎵','🎶','🎷','🎸','🎹','🎺','🎻','🎼','🎽','🎾','🎿','🏀','🏁','🏂','🏃','‍','♀','️','♂','🏄','🏅','🏆','🏇','🏈','🏉','🏊','🏋','🏌','🏍','🏎','🏏','🏐','🏑','🏒','🏓','🏔','🏕','🏖','🏗','🏘','🏙','🏚','🏛','🏜','🏝','🏞','🏟','🏠','🏡','🏢','🏣','🏤','🏥','🏦','🏧','🏨','🏩','🏪','🏫','🏬','🏭','🏮','🏯','🏰','🏳','🏴','☠','󠁧','󠁢','󠁥','󠁮','󠁿','󠁳','󠁣','󠁴','󠁷','󠁬','🏵','🏷','🏸','🏹','🏺','🐀','🐁','🐂','🐃','🐄','🐅','🐆','🐇','🐈','🐉','🐊','🐋','🐌','🐍','🐎','🐏','🐐','🐑','🐒','🐓','🐔','🐕','🐖','🐗','🐘','🐙','🐚','🐛','🐜','🐝','🐞','🐟','🐠','🐡','🐢','🐣','🐤','🐥','🐦','🐧','🐨','🐩','🐪','🐫','🐬','🐭','🐮','🐯','🐰','🐱','🐲','🐳','🐴','🐵','🐶','🐷','🐸','🐹','🐺','🐻','🐼','🐽','🐾','🐿','👀','👁','🗨','👂','👃','👄','👅','👆','👇','👈','👉','👊','👋','👌','👍','👎','👏','👐','👑','👒','👓','👔','👕','👖','👗','👘','👙','👚','👛','👜','👝','👞','👟','👠','👡','👢','👣','👤','👥','👦','👧','👨','💻','💼','🔧','🔬','🚀','🚒','⚕','⚖','✈','👩','❤','💋','👪','👫','👬','👭','👮','👯','👰','👱','👲','👳','👴','👵','👶','👷','👸','👹','👺','👻','👼','👽','👾','👿','💀','💁','💂','💃','💄','💅','💆','💇','💈','💉','💊','💌','💍','💎','💏','💐','💑','💒','💓','💔','💕','💖','💗','💘','💙','💚','💛','💜','💝','💞','💟','💠','💡','💢','💣','💤','💥','💦','💧','💨','💩','💪','💫','💬','💭','💮','💯','💰','💱','💲','💳','💴','💵','💶','💷','💸','💹','💺','💽','💾','💿','📀','📁','📂','📃','📄','📅','📆','📇','📈','📉','📊','📋','📌','📍','📎','📏','📐','📑','📒','📓','📔','📕','📖','📗','📘','📙','📚','📛','📜','📝','📞','📟','📠','📡','📢','📣','📤','📥','📦','📧','📨','📩','📪','📫','📬','📭','📮','📯','📰','📱','📲','📳','📴','📵','📶','📷','📸','📹','📺','📻','📼','📽','📿','🔀','🔁','🔂','🔃','🔄','🔅','🔆','🔇','🔈','🔉','🔊','🔋','🔌','🔍','🔎','🔏','🔐','🔑','🔒','🔓','🔔','🔕','🔖','🔗','🔘','🔙','🔚','🔛','🔜','🔝','🔞','🔟','🔠','🔡','🔢','🔣','🔤','🔥','🔦','🔨','🔩','🔪','🔫','🔭','🔮','🔯','🔰','🔱','🔲','🔳','🔴','🔵','🔶','🔷','🔸','🔹','🔺','🔻','🔼','🔽','🕉','🕊','🕋','🕌','🕍','🕎','🕐','🕑','🕒','🕓','🕔','🕕','🕖','🕗','🕘','🕙','🕚','🕛','🕜','🕝','🕞','🕟','🕠','🕡','🕢','🕣','🕤','🕥','🕦','🕧','🕯','🕰','🕳','🕴','🕵','🕶','🕷','🕸','🕹','🕺','🖇','🖊','🖋','🖌','🖍','🖐','🖕','🖖','🖤','🖥','🖨','🖱','🖲','🖼','🗂','🗃','🗄','🗑','🗒','🗓','🗜','🗝','🗞','🗡','🗣','🗯','🗳','🗺','🗻','🗼','🗽','🗾','🗿','😀','😁','😂','😃','😄','😅','😆','😇','😈','😉','😊','😋','😌','😍','😎','😏','😐','😑','😒','😓','😔','😕','😖','😗','😘','😙','😚','😛','😜','😝','😞','😟','😠','😡','😢','😣','😤','😥','😦','😧','😨','😩','😪','😫','😬','😭','😮','😯','😰','😱','😲','😳','😴','😵','😶','😷','😸','😹','😺','😻','😼','😽','😾','😿','🙀','🙁','🙂','🙃','🙄','🙅','🙆','🙇','🙈','🙉','🙊','🙋','🙌','🙍','🙎','🙏','🚁','🚂','🚃','🚄','🚅','🚆','🚇','🚈','🚉','🚊','🚋','🚌','🚍','🚎','🚏','🚐','🚑','🚓','🚔','🚕','🚖','🚗','🚘','🚙','🚚','🚛','🚜','🚝','🚞','🚟','🚠','🚡','🚢','🚣','🚤','🚥','🚦','🚧','🚨','🚩','🚪','🚫','🚬','🚭','🚮','🚯','🚰','🚱','🚲','🚳','🚴','🚵','🚶','🚷','🚸','🚹','🚺','🚻','🚼','🚽','🚾','🚿','🛀','🛁','🛂','🛃','🛄','🛅','🛋','🛌','🛍','🛎','🛏','🛐','🛑','🛒','🛠','🛡','🛢','🛣','🛤','🛥','🛩','🛫','🛬','🛰','🛳','🛴','🛵','🛶','🛷','🛸','🤐','🤑','🤒','🤓','🤔','🤕','🤖','🤗','🤘','🤙','🤚','🤛','🤜','🤝','🤞','🤟','🤠','🤡','🤢','🤣','🤤','🤥','🤦','🤧','🤨','🤩','🤪','🤫','🤬','🤭','🤮','🤯','🤰','🤱','🤲','🤳','🤴','🤵','🤶','🤷','🤸','🤹','🤺','🤼','🤽','🤾','🥀','🥁','🥂','🥃','🥄','🥅','🥇','🥈','🥉','🥊','🥋','🥌','🥐','🥑','🥒','🥓','🥔','🥕','🥖','🥗','🥘','🥙','🥚','🥛','🥜','🥝','🥞','🥟','🥠','🥡','🥢','🥣','🥤','🥥','🥦','🥧','🥨','🥩','🥪','🥫','🦀','🦁','🦂','🦃','🦄','🦅','🦆','🦇','🦈','🦉','🦊','🦋','🦌','🦍','🦎','🦏','🦐','🦑','🦒','🦓','🦔','🦕','🦖','🦗','🧀','🧐','🧑','🧒','🧓','🧔','🧕','🧖','🧗','🧘','🧙','🧚','🧛','🧜','🧝','🧞','🧟','🧠','🧡','🧢','🧣','🧤','🧥','🧦','‼','⁉','™','ℹ','↔','↕','↖','↗','↘','↙','↩','↪','⃣','⌚','⌛','⌨','⏏','⏩','⏪','⏫','⏬','⏭','⏮','⏯','⏰','⏱','⏲','⏳','⏸','⏹','⏺','Ⓜ','▪','▫','▶','◀','◻','◼','◽','◾','☀','☁','☂','☃','☄','☎','☑','☔','☕','☘','☝','☢','☣','☦','☪','☮','☯','☸','☹','☺','♈','♉','♊','♋','♌','♍','♎','♏','♐','♑','♒','♓','♠','♣','♥','♦','♨','♻','♿','⚒','⚓','⚔','⚗','⚙','⚛','⚜','⚠','⚡','⚪','⚫','⚰','⚱','⚽','⚾','⛄','⛅','⛈','⛎','⛏','⛑','⛓','⛔','⛩','⛪','⛰','⛱','⛲','⛳','⛴','⛵','⛷','⛸','⛹','⛺','⛽','✂','✅','✉','✊','✋','✌','✍','✏','✒','✔','✖','✝','✡','✨','✳','✴','❄','❇','❌','❎','❓','❔','❕','❗','❣','➕','➖','➗','➡','➰','➿','⤴','⤵','⬅','⬆','⬇','⬛','⬜','⭐','⭕','〰','〽','㊗','㊙','');
5318 // END: emoji arrays
5319
5320 if ( 'entities' === $type ) {
5321 return $entities;
5322 }
5323
5324 return $partials;
5325}
5326
5327/**
5328 * Shorten a URL, to be used as link text.
5329 *
5330 * @since 1.2.0
5331 * @since 4.4.0 Moved to wp-includes/formatting.php from wp-admin/includes/misc.php and added $length param.
5332 *
5333 * @param string $url URL to shorten.
5334 * @param int $length Optional. Maximum length of the shortened URL. Default 35 characters.
5335 * @return string Shortened URL.
5336 */
5337function url_shorten( $url, $length = 35 ) {
5338 $stripped = str_replace( array( 'https://', 'http://', 'www.' ), '', $url );
5339 $short_url = untrailingslashit( $stripped );
5340
5341 if ( strlen( $short_url ) > $length ) {
5342 $short_url = substr( $short_url, 0, $length - 3 ) . '…';
5343 }
5344 return $short_url;
5345}
5346
5347/**
5348 * Sanitizes a hex color.
5349 *
5350 * Returns either '', a 3 or 6 digit hex color (with #), or nothing.
5351 * For sanitizing values without a #, see sanitize_hex_color_no_hash().
5352 *
5353 * @since 3.4.0
5354 *
5355 * @param string $color
5356 * @return string|void
5357 */
5358function sanitize_hex_color( $color ) {
5359 if ( '' === $color ) {
5360 return '';
5361 }
5362
5363 // 3 or 6 hex digits, or the empty string.
5364 if ( preg_match('|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) ) {
5365 return $color;
5366 }
5367}
5368
5369/**
5370 * Sanitizes a hex color without a hash. Use sanitize_hex_color() when possible.
5371 *
5372 * Saving hex colors without a hash puts the burden of adding the hash on the
5373 * UI, which makes it difficult to use or upgrade to other color types such as
5374 * rgba, hsl, rgb, and html color names.
5375 *
5376 * Returns either '', a 3 or 6 digit hex color (without a #), or null.
5377 *
5378 * @since 3.4.0
5379 *
5380 * @param string $color
5381 * @return string|null
5382 */
5383function sanitize_hex_color_no_hash( $color ) {
5384 $color = ltrim( $color, '#' );
5385
5386 if ( '' === $color ) {
5387 return '';
5388 }
5389
5390 return sanitize_hex_color( '#' . $color ) ? $color : null;
5391}
5392
5393/**
5394 * Ensures that any hex color is properly hashed.
5395 * Otherwise, returns value untouched.
5396 *
5397 * This method should only be necessary if using sanitize_hex_color_no_hash().
5398 *
5399 * @since 3.4.0
5400 *
5401 * @param string $color
5402 * @return string
5403 */
5404function maybe_hash_hex_color( $color ) {
5405 if ( $unhashed = sanitize_hex_color_no_hash( $color ) ) {
5406 return '#' . $unhashed;
5407 }
5408
5409 return $color;
5410}