Make WordPress Core

Ticket #40135: formatting.php

File formatting.php, 285.8 KB (added by ayubi, 7 years ago)
Line 
1<?php
2/**
3 * Main WordPress Formatting API.
4 *
5 * Handles many functions for formatting output.
6 *
7 * @package WordPress
8 */
9
10/**
11 * Replaces common plain text characters into formatted entities
12 *
13 * As an example,
14 *
15 *     'cause today's effort makes it worth tomorrow's "holiday" ...
16 *
17 * Becomes:
18 *
19 *     &#8217;cause today&#8217;s effort makes it worth tomorrow&#8217;s &#8220;holiday&#8221; &#8230;
20 *
21 * Code within certain html blocks are skipped.
22 *
23 * Do not use this function before the {@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{
53    global $wp_cockneyreplace, $shortcode_tags;
54    static $static_characters            = null,
55        $static_replacements             = null,
56        $dynamic_characters              = null,
57        $dynamic_replacements            = null,
58        $default_no_texturize_tags       = null,
59        $default_no_texturize_shortcodes = null,
60        $run_texturize                   = true,
61        $apos                            = null,
62        $prime                           = null,
63        $double_prime                    = null,
64        $opening_quote                   = null,
65        $closing_quote                   = null,
66        $opening_single_quote            = null,
67        $closing_single_quote            = null,
68        $open_q_flag                     = '<!--oq-->',
69        $open_sq_flag                    = '<!--osq-->',
70        $apos_flag                       = '<!--apos-->';
71
72    // If there's nothing to do, just stop.
73    if (empty($text) || false === $run_texturize) {
74        return $text;
75    }
76
77    // Set up static variables. Run once only.
78    if ($reset || ! isset($static_characters)) {
79        /**
80         * Filters whether to skip running wptexturize().
81         *
82         * Passing false to the filter will effectively short-circuit wptexturize().
83         * returning the original text passed to the function instead.
84         *
85         * The filter runs only once, the first time wptexturize() is called.
86         *
87         * @since 4.0.0
88         *
89         * @see wptexturize()
90         *
91         * @param bool $run_texturize Whether to short-circuit wptexturize().
92         */
93        $run_texturize = apply_filters('run_wptexturize', $run_texturize);
94        if (false === $run_texturize) {
95            return $text;
96        }
97
98        /* translators: opening curly double quote */
99        $opening_quote = _x('&#8220;', 'opening curly double quote');
100        /* translators: closing curly double quote */
101        $closing_quote = _x('&#8221;', 'closing curly double quote');
102
103        /* translators: apostrophe, for example in 'cause or can't */
104        $apos = _x('&#8217;', 'apostrophe');
105
106        /* translators: prime, for example in 9' (nine feet) */
107        $prime = _x('&#8242;', 'prime');
108        /* translators: double prime, for example in 9" (nine inches) */
109        $double_prime = _x('&#8243;', 'double prime');
110
111        /* translators: opening curly single quote */
112        $opening_single_quote = _x('&#8216;', 'opening curly single quote');
113        /* translators: closing curly single quote */
114        $closing_single_quote = _x('&#8217;', 'closing curly single quote');
115
116        /* translators: en dash */
117        $en_dash = _x('&#8211;', 'en dash');
118        /* translators: em dash */
119        $em_dash = _x('&#8212;', 'em dash');
120
121        $default_no_texturize_tags       = array( 'pre', 'code', 'kbd', 'style', 'script', 'tt' );
122        $default_no_texturize_shortcodes = array( 'code' );
123
124        // if a plugin has provided an autocorrect array, use it
125        if (isset($wp_cockneyreplace)) {
126            $cockney        = array_keys($wp_cockneyreplace);
127            $cockneyreplace = array_values($wp_cockneyreplace);
128        } else {
129            /* translators: This is a comma-separated list of words that defy the syntax of quotations in normal use,
130             * for example...  'We do not have enough words yet' ... is a typical quoted phrase.  But when we write
131             * lines of code 'til we have enough of 'em, then we need to insert apostrophes instead of quotes.
132             */
133            $cockney = explode(
134                ',',
135                _x(
136                    "'tain't,'twere,'twas,'tis,'twill,'til,'bout,'nuff,'round,'cause,'em",
137                    'Comma-separated list of words to texturize in your language'
138                )
139            );
140
141            $cockneyreplace = explode(
142                ',',
143                _x(
144                    '&#8217;tain&#8217;t,&#8217;twere,&#8217;twas,&#8217;tis,&#8217;twill,&#8217;til,&#8217;bout,&#8217;nuff,&#8217;round,&#8217;cause,&#8217;em',
145                    'Comma-separated list of replacement words in your language'
146                )
147            );
148        }
149
150        $static_characters   = array_merge(array( '...', '``', '\'\'', ' (tm)' ), $cockney);
151        $static_replacements = array_merge(array( '&#8230;', $opening_quote, $closing_quote, ' &#8482;' ), $cockneyreplace);
152
153        // Pattern-based replacements of characters.
154        // Sort the remaining patterns into several arrays for performance tuning.
155        $dynamic_characters   = array(
156            'apos'  => array(),
157            'quote' => array(),
158            'dash'  => array(),
159        );
160        $dynamic_replacements = array(
161            'apos'  => array(),
162            'quote' => array(),
163            'dash'  => array(),
164        );
165        $dynamic              = array();
166        $spaces               = wp_spaces_regexp();
167
168        // '99' and '99" are ambiguous among other patterns; assume it's an abbreviated year at the end of a quotation.
169        if ("'" !== $apos || "'" !== $closing_single_quote) {
170            $dynamic[ '/\'(\d\d)\'(?=\Z|[.,:;!?)}\-\]]|&gt;|' . $spaces . ')/' ] = $apos_flag . '$1' . $closing_single_quote;
171        }
172        if ("'" !== $apos || '"' !== $closing_quote) {
173            $dynamic[ '/\'(\d\d)"(?=\Z|[.,:;!?)}\-\]]|&gt;|' . $spaces . ')/' ] = $apos_flag . '$1' . $closing_quote;
174        }
175
176        // '99 '99s '99's (apostrophe)  But never '9 or '99% or '999 or '99.0.
177        if ("'" !== $apos) {
178            $dynamic['/\'(?=\d\d(?:\Z|(?![%\d]|[.,]\d)))/'] = $apos_flag;
179        }
180
181        // Quoted Numbers like '0.42'
182        if ("'" !== $opening_single_quote && "'" !== $closing_single_quote) {
183            $dynamic[ '/(?<=\A|' . $spaces . ')\'(\d[.,\d]*)\'/' ] = $open_sq_flag . '$1' . $closing_single_quote;
184        }
185
186        // Single quote at start, or preceded by (, {, <, [, ", -, or spaces.
187        if ("'" !== $opening_single_quote) {
188            $dynamic[ '/(?<=\A|[([{"\-]|&lt;|' . $spaces . ')\'/' ] = $open_sq_flag;
189        }
190
191        // Apostrophe in a word.  No spaces, double apostrophes, or other punctuation.
192        if ("'" !== $apos) {
193            $dynamic[ '/(?<!' . $spaces . ')\'(?!\Z|[.,:;!?"\'(){}[\]\-]|&[lg]t;|' . $spaces . ')/' ] = $apos_flag;
194        }
195
196        $dynamic_characters['apos']   = array_keys($dynamic);
197        $dynamic_replacements['apos'] = array_values($dynamic);
198        $dynamic                      = array();
199
200        // Quoted Numbers like "42"
201        if ('"' !== $opening_quote && '"' !== $closing_quote) {
202            $dynamic[ '/(?<=\A|' . $spaces . ')"(\d[.,\d]*)"/' ] = $open_q_flag . '$1' . $closing_quote;
203        }
204
205        // Double quote at start, or preceded by (, {, <, [, -, or spaces, and not followed by spaces.
206        if ('"' !== $opening_quote) {
207            $dynamic[ '/(?<=\A|[([{\-]|&lt;|' . $spaces . ')"(?!' . $spaces . ')/' ] = $open_q_flag;
208        }
209
210        $dynamic_characters['quote']   = array_keys($dynamic);
211        $dynamic_replacements['quote'] = array_values($dynamic);
212        $dynamic                       = array();
213
214        // Dashes and spaces
215        $dynamic['/---/'] = $em_dash;
216        $dynamic[ '/(?<=^|' . $spaces . ')--(?=$|' . $spaces . ')/' ] = $em_dash;
217        $dynamic['/(?<!xn)--/']                                       = $en_dash;
218        $dynamic[ '/(?<=^|' . $spaces . ')-(?=$|' . $spaces . ')/' ]  = $en_dash;
219
220        $dynamic_characters['dash']   = array_keys($dynamic);
221        $dynamic_replacements['dash'] = array_values($dynamic);
222    }
223
224    // Must do this every time in case plugins use these filters in a context sensitive manner
225    /**
226     * Filters the list of HTML elements not to texturize.
227     *
228     * @since 2.8.0
229     *
230     * @param array $default_no_texturize_tags An array of HTML element names.
231     */
232    $no_texturize_tags = apply_filters('no_texturize_tags', $default_no_texturize_tags);
233    /**
234     * Filters the list of shortcodes not to texturize.
235     *
236     * @since 2.8.0
237     *
238     * @param array $default_no_texturize_shortcodes An array of shortcode names.
239     */
240    $no_texturize_shortcodes = apply_filters('no_texturize_shortcodes', $default_no_texturize_shortcodes);
241
242    $no_texturize_tags_stack       = array();
243    $no_texturize_shortcodes_stack = array();
244
245    // Look for shortcodes and HTML elements.
246
247    preg_match_all('@\[/?([^<>&/\[\]\x00-\x20=]++)@', $text, $matches);
248    $tagnames         = array_intersect(array_keys($shortcode_tags), $matches[1]);
249    $found_shortcodes = ! empty($tagnames);
250    $shortcode_regex  = $found_shortcodes ? _get_wptexturize_shortcode_regex($tagnames) : '';
251    $regex            = _get_wptexturize_split_regex($shortcode_regex);
252
253    $textarr = preg_split($regex, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
254
255    foreach ($textarr as &$curl) {
256        // Only call _wptexturize_pushpop_element if $curl is a delimiter.
257        $first = $curl[0];
258        if ('<' === $first) {
259            if ('<!--' === substr($curl, 0, 4)) {
260                // This is an HTML comment delimiter.
261                continue;
262            } else {
263                // This is an HTML element delimiter.
264
265                // Replace each & with &#038; unless it already looks like an entity.
266                $curl = preg_replace('/&(?!#(?:\d+|x[a-f0-9]+);|[a-z1-4]{1,8};)/i', '&#038;', $curl);
267
268                _wptexturize_pushpop_element($curl, $no_texturize_tags_stack, $no_texturize_tags);
269            }
270        } elseif ('' === trim($curl)) {
271            // This is a newline between delimiters.  Performance improves when we check this.
272            continue;
273        } elseif ('[' === $first && $found_shortcodes && 1 === preg_match('/^' . $shortcode_regex . '$/', $curl)) {
274            // This is a shortcode delimiter.
275
276            if ('[[' !== substr($curl, 0, 2) && ']]' !== substr($curl, -2)) {
277                // Looks like a normal shortcode.
278                _wptexturize_pushpop_element($curl, $no_texturize_shortcodes_stack, $no_texturize_shortcodes);
279            } else {
280                // Looks like an escaped shortcode.
281                continue;
282            }
283        } elseif (empty($no_texturize_shortcodes_stack) && empty($no_texturize_tags_stack)) {
284            // This is neither a delimiter, nor is this content inside of no_texturize pairs.  Do texturize.
285
286            $curl = str_replace($static_characters, $static_replacements, $curl);
287
288            if (false !== strpos($curl, "'")) {
289                $curl = preg_replace($dynamic_characters['apos'], $dynamic_replacements['apos'], $curl);
290                $curl = wptexturize_primes($curl, "'", $prime, $open_sq_flag, $closing_single_quote);
291                $curl = str_replace($apos_flag, $apos, $curl);
292                $curl = str_replace($open_sq_flag, $opening_single_quote, $curl);
293            }
294            if (false !== strpos($curl, '"')) {
295                $curl = preg_replace($dynamic_characters['quote'], $dynamic_replacements['quote'], $curl);
296                $curl = wptexturize_primes($curl, '"', $double_prime, $open_q_flag, $closing_quote);
297                $curl = str_replace($open_q_flag, $opening_quote, $curl);
298            }
299            if (false !== strpos($curl, '-')) {
300                $curl = preg_replace($dynamic_characters['dash'], $dynamic_replacements['dash'], $curl);
301            }
302
303            // 9x9 (times), but never 0x9999
304            if (1 === preg_match('/(?<=\d)x\d/', $curl)) {
305                // Searching for a digit is 10 times more expensive than for the x, so we avoid doing this one!
306                $curl = preg_replace('/\b(\d(?(?<=0)[\d\.,]+|[\d\.,]*))x(\d[\d\.,]*)\b/', '$1&#215;$2', $curl);
307            }
308
309            // Replace each & with &#038; unless it already looks like an entity.
310            $curl = preg_replace('/&(?!#(?:\d+|x[a-f0-9]+);|[a-z1-4]{1,8};)/i', '&#038;', $curl);
311        }
312    }
313
314    return implode('', $textarr);
315}
316
317/**
318 * Implements a logic tree to determine whether or not "7'." represents seven feet,
319 * then converts the special char into either a prime char or a closing quote char.
320 *
321 * @since 4.3.0
322 *
323 * @param string $haystack    The plain text to be searched.
324 * @param string $needle      The character to search for such as ' or ".
325 * @param string $prime       The prime char to use for replacement.
326 * @param string $open_quote  The opening quote char. Opening quote replacement must be
327 *                            accomplished already.
328 * @param string $close_quote The closing quote char to use for replacement.
329 * @return string The $haystack value after primes and quotes replacements.
330 */
331function wptexturize_primes($haystack, $needle, $prime, $open_quote, $close_quote)
332{
333    $spaces           = wp_spaces_regexp();
334    $flag             = '<!--wp-prime-or-quote-->';
335    $quote_pattern    = "/$needle(?=\\Z|[.,:;!?)}\\-\\]]|&gt;|" . $spaces . ')/';
336    $prime_pattern    = "/(?<=\\d)$needle/";
337    $flag_after_digit = "/(?<=\\d)$flag/";
338    $flag_no_digit    = "/(?<!\\d)$flag/";
339
340    $sentences = explode($open_quote, $haystack);
341
342    foreach ($sentences as $key => &$sentence) {
343        if (false === strpos($sentence, $needle)) {
344            continue;
345        } elseif (0 !== $key && 0 === substr_count($sentence, $close_quote)) {
346            $sentence = preg_replace($quote_pattern, $flag, $sentence, -1, $count);
347            if ($count > 1) {
348                // This sentence appears to have multiple closing quotes.  Attempt Vulcan logic.
349                $sentence = preg_replace($flag_no_digit, $close_quote, $sentence, -1, $count2);
350                if (0 === $count2) {
351                    // Try looking for a quote followed by a period.
352                    $count2 = substr_count($sentence, "$flag.");
353                    if ($count2 > 0) {
354                        // Assume the rightmost quote-period match is the end of quotation.
355                        $pos = strrpos($sentence, "$flag.");
356                    } else {
357                        // When all else fails, make the rightmost candidate a closing quote.
358                        // This is most likely to be problematic in the context of bug #18549.
359                        $pos = strrpos($sentence, $flag);
360                    }
361                    $sentence = substr_replace($sentence, $close_quote, $pos, strlen($flag));
362                }
363                // Use conventional replacement on any remaining primes and quotes.
364                $sentence = preg_replace($prime_pattern, $prime, $sentence);
365                $sentence = preg_replace($flag_after_digit, $prime, $sentence);
366                $sentence = str_replace($flag, $close_quote, $sentence);
367            } elseif (1 == $count) {
368                // Found only one closing quote candidate, so give it priority over primes.
369                $sentence = str_replace($flag, $close_quote, $sentence);
370                $sentence = preg_replace($prime_pattern, $prime, $sentence);
371            } else {
372                // No closing quotes found.  Just run primes pattern.
373                $sentence = preg_replace($prime_pattern, $prime, $sentence);
374            }
375        } else {
376            $sentence = preg_replace($prime_pattern, $prime, $sentence);
377            $sentence = preg_replace($quote_pattern, $close_quote, $sentence);
378        }
379        if ('"' == $needle && false !== strpos($sentence, '"')) {
380            $sentence = str_replace('"', $close_quote, $sentence);
381        }
382    }
383
384    return implode($open_quote, $sentences);
385}
386
387/**
388 * Search for disabled element tags. Push element to stack on tag open and pop
389 * on tag close.
390 *
391 * Assumes first char of $text is tag opening and last char is tag closing.
392 * Assumes second char of $text is optionally '/' to indicate closing as in </html>.
393 *
394 * @since 2.9.0
395 * @access private
396 *
397 * @param string $text Text to check. Must be a tag like `<html>` or `[shortcode]`.
398 * @param array  $stack List of open tag elements.
399 * @param array  $disabled_elements The tag names to match against. Spaces are not allowed in tag names.
400 */
401function _wptexturize_pushpop_element($text, &$stack, $disabled_elements)
402{
403    // Is it an opening tag or closing tag?
404    if (isset($text[1]) && '/' !== $text[1]) {
405        $opening_tag = true;
406        $name_offset = 1;
407    } elseif (0 == count($stack)) {
408        // Stack is empty. Just stop.
409        return;
410    } else {
411        $opening_tag = false;
412        $name_offset = 2;
413    }
414
415    // Parse out the tag name.
416    $space = strpos($text, ' ');
417    if (false === $space) {
418        $space = -1;
419    } else {
420        $space -= $name_offset;
421    }
422    $tag = substr($text, $name_offset, $space);
423
424    // Handle disabled tags.
425    if (in_array($tag, $disabled_elements)) {
426        if ($opening_tag) {
427            /*
428             * This disables texturize until we find a closing tag of our type
429             * (e.g. <pre>) even if there was invalid nesting before that
430             *
431             * Example: in the case <pre>sadsadasd</code>"baba"</pre>
432             *          "baba" won't be texturize
433             */
434
435            array_push($stack, $tag);
436        } elseif (end($stack) == $tag) {
437            array_pop($stack);
438        }
439    }
440}
441
442/**
443 * Replaces double line-breaks with paragraph elements.
444 *
445 * A group of regex replaces used to identify text formatted with newlines and
446 * replace double line-breaks with HTML paragraph tags. The remaining line-breaks
447 * after conversion become <<br />> tags, unless $br is set to '0' or 'false'.
448 *
449 * @since 0.71
450 *
451 * @param string $pee The text which has to be formatted.
452 * @param bool   $br  Optional. If set, this will convert all remaining line-breaks
453 *                    after paragraphing. Default true.
454 * @return string Text which has been converted into correct paragraph tags.
455 */
456function wpautop($pee, $br = true)
457{
458    $pre_tags = array();
459
460    if (trim($pee) === '') {
461        return '';
462    }
463
464    // Just to make things a little easier, pad the end.
465    $pee = $pee . "\n";
466
467    /*
468     * Pre tags shouldn't be touched by autop.
469     * Replace pre tags with placeholders and bring them back after autop.
470     */
471    if (strpos($pee, '<pre') !== false) {
472        $pee_parts = explode('</pre>', $pee);
473        $last_pee  = array_pop($pee_parts);
474        $pee       = '';
475        $i         = 0;
476
477        foreach ($pee_parts as $pee_part) {
478            $start = strpos($pee_part, '<pre');
479
480            // Malformed html?
481            if ($start === false) {
482                $pee .= $pee_part;
483                continue;
484            }
485
486            $name              = "<pre wp-pre-tag-$i></pre>";
487            $pre_tags[ $name ] = substr($pee_part, $start) . '</pre>';
488
489            $pee .= substr($pee_part, 0, $start) . $name;
490            $i++;
491        }
492
493        $pee .= $last_pee;
494    }
495    // Change multiple <br>s into two line breaks, which will turn into paragraphs.
496    $pee = preg_replace('|<br\s*/?>\s*<br\s*/?>|', "\n\n", $pee);
497
498    $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)';
499
500    // Add a double line break above block-level opening tags.
501    $pee = preg_replace('!(<' . $allblocks . '[\s/>])!', "\n\n$1", $pee);
502
503    // Add a double line break below block-level closing tags.
504    $pee = preg_replace('!(</' . $allblocks . '>)!', "$1\n\n", $pee);
505
506    // Standardize newline characters to "\n".
507    $pee = str_replace(array( "\r\n", "\r" ), "\n", $pee);
508
509    // Find newlines in all elements and add placeholders.
510    $pee = wp_replace_in_html_tags($pee, array( "\n" => ' <!-- wpnl --> ' ));
511
512    // Collapse line breaks before and after <option> elements so they don't get autop'd.
513    if (strpos($pee, '<option') !== false) {
514        $pee = preg_replace('|\s*<option|', '<option', $pee);
515        $pee = preg_replace('|</option>\s*|', '</option>', $pee);
516    }
517
518    /*
519     * Collapse line breaks inside <object> elements, before <param> and <embed> elements
520     * so they don't get autop'd.
521     */
522    if (strpos($pee, '</object>') !== false) {
523        $pee = preg_replace('|(<object[^>]*>)\s*|', '$1', $pee);
524        $pee = preg_replace('|\s*</object>|', '</object>', $pee);
525        $pee = preg_replace('%\s*(</?(?:param|embed)[^>]*>)\s*%', '$1', $pee);
526    }
527
528    /*
529     * Collapse line breaks inside <audio> and <video> elements,
530     * before and after <source> and <track> elements.
531     */
532    if (strpos($pee, '<source') !== false || strpos($pee, '<track') !== false) {
533        $pee = preg_replace('%([<\[](?:audio|video)[^>\]]*[>\]])\s*%', '$1', $pee);
534        $pee = preg_replace('%\s*([<\[]/(?:audio|video)[>\]])%', '$1', $pee);
535        $pee = preg_replace('%\s*(<(?:source|track)[^>]*>)\s*%', '$1', $pee);
536    }
537
538    // Collapse line breaks before and after <figcaption> elements.
539    if (strpos($pee, '<figcaption') !== false) {
540        $pee = preg_replace('|\s*(<figcaption[^>]*>)|', '$1', $pee);
541        $pee = preg_replace('|</figcaption>\s*|', '</figcaption>', $pee);
542    }
543
544    // Remove more than two contiguous line breaks.
545    $pee = preg_replace("/\n\n+/", "\n\n", $pee);
546
547    // Split up the contents into an array of strings, separated by double line breaks.
548    $pees = preg_split('/\n\s*\n/', $pee, -1, PREG_SPLIT_NO_EMPTY);
549
550    // Reset $pee prior to rebuilding.
551    $pee = '';
552
553    // Rebuild the content as a string, wrapping every bit with a <p>.
554    foreach ($pees as $tinkle) {
555        $pee .= '<p>' . trim($tinkle, "\n") . "</p>\n";
556    }
557
558    // Under certain strange conditions it could create a P of entirely whitespace.
559    $pee = preg_replace('|<p>\s*</p>|', '', $pee);
560
561    // Add a closing <p> inside <div>, <address>, or <form> tag if missing.
562    $pee = preg_replace('!<p>([^<]+)</(div|address|form)>!', '<p>$1</p></$2>', $pee);
563
564    // If an opening or closing block element tag is wrapped in a <p>, unwrap it.
565    $pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)\s*</p>!', '$1', $pee);
566
567    // In some cases <li> may get wrapped in <p>, fix them.
568    $pee = preg_replace('|<p>(<li.+?)</p>|', '$1', $pee);
569
570    // If a <blockquote> is wrapped with a <p>, move it inside the <blockquote>.
571    $pee = preg_replace('|<p><blockquote([^>]*)>|i', '<blockquote$1><p>', $pee);
572    $pee = str_replace('</blockquote></p>', '</p></blockquote>', $pee);
573
574    $pee = preg_replace('/(img\s)?(^">|<\/p>)/', "", $pee);
575
576    // If an opening or closing block element tag is preceded by an opening <p> tag, remove it.
577    $pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)!', '$1', $pee);
578
579    // If an opening or closing block element tag is followed by a closing <p> tag, remove it.
580    $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*</p>!', '$1', $pee);
581
582    // Optionally insert line breaks.
583    if ($br) {
584        // Replace newlines that shouldn't be touched with a placeholder.
585        $pee = preg_replace_callback('/<(script|style).*?<\/\\1>/s', '_autop_newline_preservation_helper', $pee);
586
587        // Normalize <br>
588        $pee = str_replace(array( '<br>', '<br/>' ), '<br />', $pee);
589
590        // Replace any new line characters that aren't preceded by a <br /> with a <br />.
591        $pee = preg_replace('|(?<!<br />)\s*\n|', "<br />\n", $pee);
592
593        // Replace newline placeholders with newlines.
594        $pee = str_replace('<WPPreserveNewline />', "\n", $pee);
595    }
596
597    // If a <br /> tag is after an opening or closing block tag, remove it.
598    $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*<br />!', '$1', $pee);
599
600    // If a <br /> tag is before a subset of opening or closing block tags, remove it.
601    $pee = preg_replace('!<br />(\s*</?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)!', '$1', $pee);
602    $pee = preg_replace("|\n</p>$|", '</p>', $pee);
603
604    // Replace placeholder <pre> tags with their original content.
605    if (! empty($pre_tags)) {
606        $pee = str_replace(array_keys($pre_tags), array_values($pre_tags), $pee);
607    }
608
609    // Restore newlines in all elements.
610    if (false !== strpos($pee, '<!-- wpnl -->')) {
611        $pee = str_replace(array( ' <!-- wpnl --> ', '<!-- wpnl -->' ), "\n", $pee);
612    }
613
614    return $pee;
615}
616
617/**
618 * Separate HTML elements and comments from the text.
619 *
620 * @since 4.2.4
621 *
622 * @param string $input The text which has to be formatted.
623 * @return array The formatted text.
624 */
625function wp_html_split($input)
626{
627    return preg_split(get_html_split_regex(), $input, -1, PREG_SPLIT_DELIM_CAPTURE);
628}
629
630/**
631 * Retrieve the regular expression for an HTML element.
632 *
633 * @since 4.4.0
634 *
635 * @staticvar string $regex
636 *
637 * @return string The regular expression
638 */
639function get_html_split_regex()
640{
641    static $regex;
642
643    if (! isset($regex)) {
644                // phpcs:disable Squiz.Strings.ConcatenationSpacing.PaddingFound -- don't remove regex indentation
645        $comments =
646            '!'             // Start of comment, after the <.
647            . '(?:'         // Unroll the loop: Consume everything until --> is found.
648            .     '-(?!->)' // Dash not followed by end of comment.
649            .     '[^\-]*+' // Consume non-dashes.
650            . ')*+'         // Loop possessively.
651            . '(?:-->)?';   // End of comment. If not found, match all input.
652
653        $cdata =
654            '!\[CDATA\['    // Start of comment, after the <.
655            . '[^\]]*+'     // Consume non-].
656            . '(?:'         // Unroll the loop: Consume everything until ]]> is found.
657            .     '](?!]>)' // One ] not followed by end of comment.
658            .     '[^\]]*+' // Consume non-].
659            . ')*+'         // Loop possessively.
660            . '(?:]]>)?';   // End of comment. If not found, match all input.
661
662        $escaped =
663            '(?='             // Is the element escaped?
664            .    '!--'
665            . '|'
666            .    '!\[CDATA\['
667            . ')'
668            . '(?(?=!-)'      // If yes, which type?
669            .     $comments
670            . '|'
671            .     $cdata
672            . ')';
673
674        $regex =
675            '/('                // Capture the entire match.
676            .     '<'           // Find start of element.
677            .     '(?'          // Conditional expression follows.
678            .         $escaped  // Find end of escaped element.
679            .     '|'           // ... else ...
680            .         '[^>]*>?' // Find end of normal element.
681            .     ')'
682            . ')/';
683                // phpcs:enable
684    }
685
686    return $regex;
687}
688
689/**
690 * Retrieve the combined regular expression for HTML and shortcodes.
691 *
692 * @access private
693 * @ignore
694 * @internal This function will be removed in 4.5.0 per Shortcode API Roadmap.
695 * @since 4.4.0
696 *
697 * @staticvar string $html_regex
698 *
699 * @param string $shortcode_regex The result from _get_wptexturize_shortcode_regex().  Optional.
700 * @return string The regular expression
701 */
702function _get_wptexturize_split_regex($shortcode_regex = '')
703{
704    static $html_regex;
705
706    if (! isset($html_regex)) {
707                // phpcs:disable Squiz.Strings.ConcatenationSpacing.PaddingFound -- don't remove regex indentation
708        $comment_regex =
709            '!'             // Start of comment, after the <.
710            . '(?:'         // Unroll the loop: Consume everything until --> is found.
711            .     '-(?!->)' // Dash not followed by end of comment.
712            .     '[^\-]*+' // Consume non-dashes.
713            . ')*+'         // Loop possessively.
714            . '(?:-->)?';   // End of comment. If not found, match all input.
715
716        $html_regex = // Needs replaced with wp_html_split() per Shortcode API Roadmap.
717            '<'                  // Find start of element.
718            . '(?(?=!--)'        // Is this a comment?
719            .     $comment_regex // Find end of comment.
720            . '|'
721            .     '[^>]*>?'      // Find end of element. If not found, match all input.
722            . ')';
723                // phpcs:enable
724    }
725
726    if (empty($shortcode_regex)) {
727        $regex = '/(' . $html_regex . ')/';
728    } else {
729        $regex = '/(' . $html_regex . '|' . $shortcode_regex . ')/';
730    }
731
732    return $regex;
733}
734
735/**
736 * Retrieve the regular expression for shortcodes.
737 *
738 * @access private
739 * @ignore
740 * @internal This function will be removed in 4.5.0 per Shortcode API Roadmap.
741 * @since 4.4.0
742 *
743 * @param array $tagnames List of shortcodes to find.
744 * @return string The regular expression
745 */
746function _get_wptexturize_shortcode_regex($tagnames)
747{
748    $tagregexp = join('|', array_map('preg_quote', $tagnames));
749    $tagregexp = "(?:$tagregexp)(?=[\\s\\]\\/])"; // Excerpt of get_shortcode_regex().
750        // phpcs:disable Squiz.Strings.ConcatenationSpacing.PaddingFound -- don't remove regex indentation
751    $regex =
752        '\['              // Find start of shortcode.
753        . '[\/\[]?'         // Shortcodes may begin with [/ or [[
754        . $tagregexp        // Only match registered shortcodes, because performance.
755        . '(?:'
756        .     '[^\[\]<>]+'  // Shortcodes do not contain other shortcodes. Quantifier critical.
757        . '|'
758        .     '<[^\[\]>]*>' // HTML elements permitted. Prevents matching ] before >.
759        . ')*+'             // Possessive critical.
760        . '\]'              // Find end of shortcode.
761        . '\]?';            // Shortcodes may end with ]]
762        // phpcs:enable
763
764    return $regex;
765}
766
767/**
768 * Replace characters or phrases within HTML elements only.
769 *
770 * @since 4.2.3
771 *
772 * @param string $haystack The text which has to be formatted.
773 * @param array $replace_pairs In the form array('from' => 'to', ...).
774 * @return string The formatted text.
775 */
776function wp_replace_in_html_tags($haystack, $replace_pairs)
777{
778    // Find all elements.
779    $textarr = wp_html_split($haystack);
780    $changed = false;
781
782    // Optimize when searching for one item.
783    if (1 === count($replace_pairs)) {
784        // Extract $needle and $replace.
785        foreach ($replace_pairs as $needle => $replace) {
786        }
787
788        // Loop through delimiters (elements) only.
789        for ($i = 1, $c = count($textarr); $i < $c; $i += 2) {
790            if (false !== strpos($textarr[ $i ], $needle)) {
791                $textarr[ $i ] = str_replace($needle, $replace, $textarr[ $i ]);
792                $changed       = true;
793            }
794        }
795    } else {
796        // Extract all $needles.
797        $needles = array_keys($replace_pairs);
798
799        // Loop through delimiters (elements) only.
800        for ($i = 1, $c = count($textarr); $i < $c; $i += 2) {
801            foreach ($needles as $needle) {
802                if (false !== strpos($textarr[ $i ], $needle)) {
803                    $textarr[ $i ] = strtr($textarr[ $i ], $replace_pairs);
804                    $changed       = true;
805                    // After one strtr() break out of the foreach loop and look at next element.
806                    break;
807                }
808            }
809        }
810    }
811
812    if ($changed) {
813        $haystack = implode($textarr);
814    }
815
816    return $haystack;
817}
818
819/**
820 * Newline preservation help function for wpautop
821 *
822 * @since 3.1.0
823 * @access private
824 *
825 * @param array $matches preg_replace_callback matches array
826 * @return string
827 */
828function _autop_newline_preservation_helper($matches)
829{
830    return str_replace("\n", '<WPPreserveNewline />', $matches[0]);
831}
832
833/**
834 * Don't auto-p wrap shortcodes that stand alone
835 *
836 * Ensures that shortcodes are not wrapped in `<p>...</p>`.
837 *
838 * @since 2.9.0
839 *
840 * @global array $shortcode_tags
841 *
842 * @param string $pee The content.
843 * @return string The filtered content.
844 */
845function shortcode_unautop($pee)
846{
847    global $shortcode_tags;
848
849    if (empty($shortcode_tags) || ! is_array($shortcode_tags)) {
850        return $pee;
851    }
852
853    $tagregexp = join('|', array_map('preg_quote', array_keys($shortcode_tags)));
854    $spaces    = wp_spaces_regexp();
855
856        // phpcs:disable Squiz.Strings.ConcatenationSpacing.PaddingFound -- don't remove regex indentation
857    $pattern =
858        '/'
859        . '<p>'                              // Opening paragraph
860        . '(?:' . $spaces . ')*+'            // Optional leading whitespace
861        . '('                                // 1: The shortcode
862        .     '\\['                          // Opening bracket
863        .     "($tagregexp)"                 // 2: Shortcode name
864        .     '(?![\\w-])'                   // Not followed by word character or hyphen
865                                             // Unroll the loop: Inside the opening shortcode tag
866        .     '[^\\]\\/]*'                   // Not a closing bracket or forward slash
867        .     '(?:'
868        .         '\\/(?!\\])'               // A forward slash not followed by a closing bracket
869        .         '[^\\]\\/]*'               // Not a closing bracket or forward slash
870        .     ')*?'
871        .     '(?:'
872        .         '\\/\\]'                   // Self closing tag and closing bracket
873        .     '|'
874        .         '\\]'                      // Closing bracket
875        .         '(?:'                      // Unroll the loop: Optionally, anything between the opening and closing shortcode tags
876        .             '[^\\[]*+'             // Not an opening bracket
877        .             '(?:'
878        .                 '\\[(?!\\/\\2\\])' // An opening bracket not followed by the closing shortcode tag
879        .                 '[^\\[]*+'         // Not an opening bracket
880        .             ')*+'
881        .             '\\[\\/\\2\\]'         // Closing shortcode tag
882        .         ')?'
883        .     ')'
884        . ')'
885        . '(?:' . $spaces . ')*+'            // optional trailing whitespace
886        . '<\\/p>'                           // closing paragraph
887        . '/';
888        // phpcs:enable
889
890    return preg_replace($pattern, '$1', $pee);
891}
892
893/**
894 * Checks to see if a string is utf8 encoded.
895 *
896 * NOTE: This function checks for 5-Byte sequences, UTF8
897 *       has Bytes Sequences with a maximum length of 4.
898 *
899 * @author bmorel at ssi dot fr (modified)
900 * @since 1.2.1
901 *
902 * @param string $str The string to be checked
903 * @return bool True if $str fits a UTF-8 model, false otherwise.
904 */
905function seems_utf8($str)
906{
907    mbstring_binary_safe_encoding();
908    $length = strlen($str);
909    reset_mbstring_encoding();
910    for ($i = 0; $i < $length; $i++) {
911        $c = ord($str[ $i ]);
912        if ($c < 0x80) {
913            $n = 0; // 0bbbbbbb
914        } elseif (( $c & 0xE0 ) == 0xC0) {
915            $n = 1; // 110bbbbb
916        } elseif (( $c & 0xF0 ) == 0xE0) {
917            $n = 2; // 1110bbbb
918        } elseif (( $c & 0xF8 ) == 0xF0) {
919            $n = 3; // 11110bbb
920        } elseif (( $c & 0xFC ) == 0xF8) {
921            $n = 4; // 111110bb
922        } elseif (( $c & 0xFE ) == 0xFC) {
923            $n = 5; // 1111110b
924        } else {
925            return false; // Does not match any model
926        }
927        for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ?
928            if (( ++$i == $length ) || ( ( ord($str[ $i ]) & 0xC0 ) != 0x80 )) {
929                return false;
930            }
931        }
932    }
933    return true;
934}
935
936/**
937 * Converts a number of special characters into their HTML entities.
938 *
939 * Specifically deals with: &, <, >, ", and '.
940 *
941 * $quote_style can be set to ENT_COMPAT to encode " to
942 * &quot;, or ENT_QUOTES to do both. Default is ENT_NOQUOTES where no quotes are encoded.
943 *
944 * @since 1.2.2
945 * @access private
946 *
947 * @staticvar string $_charset
948 *
949 * @param string     $string         The text which is to be encoded.
950 * @param int|string $quote_style    Optional. Converts double quotes if set to ENT_COMPAT,
951 *                                   both single and double if set to ENT_QUOTES or none if set to ENT_NOQUOTES.
952 *                                   Also compatible with old values; converting single quotes if set to 'single',
953 *                                   double if set to 'double' or both if otherwise set.
954 *                                   Default is ENT_NOQUOTES.
955 * @param string     $charset        Optional. The character encoding of the string. Default is false.
956 * @param bool       $double_encode  Optional. Whether to encode existing html entities. Default is false.
957 * @return string The encoded text with HTML entities.
958 */
959function _wp_specialchars($string, $quote_style = ENT_NOQUOTES, $charset = false, $double_encode = false)
960{
961    $string = (string) $string;
962
963    if (0 === strlen($string)) {
964        return '';
965    }
966
967    // Don't bother if there are no specialchars - saves some processing
968    if (! preg_match('/[&<>"\']/', $string)) {
969        return $string;
970    }
971
972    // Account for the previous behaviour of the function when the $quote_style is not an accepted value
973    if (empty($quote_style)) {
974        $quote_style = ENT_NOQUOTES;
975    } elseif (! in_array($quote_style, array( 0, 2, 3, 'single', 'double' ), true)) {
976        $quote_style = ENT_QUOTES;
977    }
978
979    // Store the site charset as a static to avoid multiple calls to wp_load_alloptions()
980    if (! $charset) {
981        static $_charset = null;
982        if (! isset($_charset)) {
983            $alloptions = wp_load_alloptions();
984            $_charset   = isset($alloptions['blog_charset']) ? $alloptions['blog_charset'] : '';
985        }
986        $charset = $_charset;
987    }
988
989    if (in_array($charset, array( 'utf8', 'utf-8', 'UTF8' ))) {
990        $charset = 'UTF-8';
991    }
992
993    $_quote_style = $quote_style;
994
995    if ($quote_style === 'double') {
996        $quote_style  = ENT_COMPAT;
997        $_quote_style = ENT_COMPAT;
998    } elseif ($quote_style === 'single') {
999        $quote_style = ENT_NOQUOTES;
1000    }
1001
1002    if (! $double_encode) {
1003        // Guarantee every &entity; is valid, convert &garbage; into &amp;garbage;
1004        // This is required for PHP < 5.4.0 because ENT_HTML401 flag is unavailable.
1005        $string = wp_kses_normalize_entities($string);
1006    }
1007
1008    $string = @htmlspecialchars($string, $quote_style, $charset, $double_encode);
1009
1010    // Back-compat.
1011    if ('single' === $_quote_style) {
1012        $string = str_replace("'", '&#039;', $string);
1013    }
1014
1015    return $string;
1016}
1017
1018/**
1019 * Converts a number of HTML entities into their special characters.
1020 *
1021 * Specifically deals with: &, <, >, ", and '.
1022 *
1023 * $quote_style can be set to ENT_COMPAT to decode " entities,
1024 * or ENT_QUOTES to do both " and '. Default is ENT_NOQUOTES where no quotes are decoded.
1025 *
1026 * @since 2.8.0
1027 *
1028 * @param string     $string The text which is to be decoded.
1029 * @param string|int $quote_style Optional. Converts double quotes if set to ENT_COMPAT,
1030 *                                both single and double if set to ENT_QUOTES or
1031 *                                none if set to ENT_NOQUOTES.
1032 *                                Also compatible with old _wp_specialchars() values;
1033 *                                converting single quotes if set to 'single',
1034 *                                double if set to 'double' or both if otherwise set.
1035 *                                Default is ENT_NOQUOTES.
1036 * @return string The decoded text without HTML entities.
1037 */
1038function wp_specialchars_decode($string, $quote_style = ENT_NOQUOTES)
1039{
1040    $string = (string) $string;
1041
1042    if (0 === strlen($string)) {
1043        return '';
1044    }
1045
1046    // Don't bother if there are no entities - saves a lot of processing
1047    if (strpos($string, '&') === false) {
1048        return $string;
1049    }
1050
1051    // Match the previous behaviour of _wp_specialchars() when the $quote_style is not an accepted value
1052    if (empty($quote_style)) {
1053        $quote_style = ENT_NOQUOTES;
1054    } elseif (! in_array($quote_style, array( 0, 2, 3, 'single', 'double' ), true)) {
1055        $quote_style = ENT_QUOTES;
1056    }
1057
1058    // More complete than get_html_translation_table( HTML_SPECIALCHARS )
1059    $single      = array(
1060        '&#039;' => '\'',
1061        '&#x27;' => '\'',
1062    );
1063    $single_preg = array(
1064        '/&#0*39;/'   => '&#039;',
1065        '/&#x0*27;/i' => '&#x27;',
1066    );
1067    $double      = array(
1068        '&quot;' => '"',
1069        '&#034;' => '"',
1070        '&#x22;' => '"',
1071    );
1072    $double_preg = array(
1073        '/&#0*34;/'   => '&#034;',
1074        '/&#x0*22;/i' => '&#x22;',
1075    );
1076    $others      = array(
1077        '&lt;'   => '<',
1078        '&#060;' => '<',
1079        '&gt;'   => '>',
1080        '&#062;' => '>',
1081        '&amp;'  => '&',
1082        '&#038;' => '&',
1083        '&#x26;' => '&',
1084    );
1085    $others_preg = array(
1086        '/&#0*60;/'   => '&#060;',
1087        '/&#0*62;/'   => '&#062;',
1088        '/&#0*38;/'   => '&#038;',
1089        '/&#x0*26;/i' => '&#x26;',
1090    );
1091
1092    if ($quote_style === ENT_QUOTES) {
1093        $translation      = array_merge($single, $double, $others);
1094        $translation_preg = array_merge($single_preg, $double_preg, $others_preg);
1095    } elseif ($quote_style === ENT_COMPAT || $quote_style === 'double') {
1096        $translation      = array_merge($double, $others);
1097        $translation_preg = array_merge($double_preg, $others_preg);
1098    } elseif ($quote_style === 'single') {
1099        $translation      = array_merge($single, $others);
1100        $translation_preg = array_merge($single_preg, $others_preg);
1101    } elseif ($quote_style === ENT_NOQUOTES) {
1102        $translation      = $others;
1103        $translation_preg = $others_preg;
1104    }
1105
1106    // Remove zero padding on numeric entities
1107    $string = preg_replace(array_keys($translation_preg), array_values($translation_preg), $string);
1108
1109    // Replace characters according to translation table
1110    return strtr($string, $translation);
1111}
1112
1113/**
1114 * Checks for invalid UTF8 in a string.
1115 *
1116 * @since 2.8.0
1117 *
1118 * @staticvar bool $is_utf8
1119 * @staticvar bool $utf8_pcre
1120 *
1121 * @param string  $string The text which is to be checked.
1122 * @param bool    $strip Optional. Whether to attempt to strip out invalid UTF8. Default is false.
1123 * @return string The checked text.
1124 */
1125function wp_check_invalid_utf8($string, $strip = false)
1126{
1127    $string = (string) $string;
1128
1129    if (0 === strlen($string)) {
1130        return '';
1131    }
1132
1133    // Store the site charset as a static to avoid multiple calls to get_option()
1134    static $is_utf8 = null;
1135    if (! isset($is_utf8)) {
1136        $is_utf8 = in_array(get_option('blog_charset'), array( 'utf8', 'utf-8', 'UTF8', 'UTF-8' ));
1137    }
1138    if (! $is_utf8) {
1139        return $string;
1140    }
1141
1142    // Check for support for utf8 in the installed PCRE library once and store the result in a static
1143    static $utf8_pcre = null;
1144    if (! isset($utf8_pcre)) {
1145        $utf8_pcre = @preg_match('/^./u', 'a');
1146    }
1147    // We can't demand utf8 in the PCRE installation, so just return the string in those cases
1148    if (! $utf8_pcre) {
1149        return $string;
1150    }
1151
1152    // preg_match fails when it encounters invalid UTF8 in $string
1153    if (1 === @preg_match('/^./us', $string)) {
1154        return $string;
1155    }
1156
1157    // Attempt to strip the bad chars if requested (not recommended)
1158    if ($strip && function_exists('iconv')) {
1159        return iconv('utf-8', 'utf-8', $string);
1160    }
1161
1162    return '';
1163}
1164
1165/**
1166 * Encode the Unicode values to be used in the URI.
1167 *
1168 * @since 1.5.0
1169 *
1170 * @param string $utf8_string
1171 * @param int    $length Max  length of the string
1172 * @return string String with Unicode encoded for URI.
1173 */
1174function utf8_uri_encode($utf8_string, $length = 0)
1175{
1176    $unicode        = '';
1177    $values         = array();
1178    $num_octets     = 1;
1179    $unicode_length = 0;
1180
1181    mbstring_binary_safe_encoding();
1182    $string_length = strlen($utf8_string);
1183    reset_mbstring_encoding();
1184
1185    for ($i = 0; $i < $string_length; $i++) {
1186        $value = ord($utf8_string[ $i ]);
1187
1188        if ($value < 128) {
1189            if ($length && ( $unicode_length >= $length )) {
1190                break;
1191            }
1192            $unicode .= chr($value);
1193            $unicode_length++;
1194        } else {
1195            if (count($values) == 0) {
1196                if ($value < 224) {
1197                    $num_octets = 2;
1198                } elseif ($value < 240) {
1199                    $num_octets = 3;
1200                } else {
1201                    $num_octets = 4;
1202                }
1203            }
1204
1205            $values[] = $value;
1206
1207            if ($length && ( $unicode_length + ( $num_octets * 3 ) ) > $length) {
1208                break;
1209            }
1210            if (count($values) == $num_octets) {
1211                for ($j = 0; $j < $num_octets; $j++) {
1212                    $unicode .= '%' . dechex($values[ $j ]);
1213                }
1214
1215                $unicode_length += $num_octets * 3;
1216
1217                $values     = array();
1218                $num_octets = 1;
1219            }
1220        }
1221    }
1222
1223    return $unicode;
1224}
1225
1226/**
1227 * Converts all accent characters to ASCII characters.
1228 *
1229 * If there are no accent characters, then the string given is just returned.
1230 *
1231 * **Accent characters converted:**
1232 *
1233 * Currency signs:
1234 *
1235 * |   Code   | Glyph | Replacement |     Description     |
1236 * | -------- | ----- | ----------- | ------------------- |
1237 * | U+00A3   | £     | (empty)     | British Pound sign  |
1238 * | U+20AC   | €     | E           | Euro sign           |
1239 *
1240 * Decompositions for Latin-1 Supplement:
1241 *
1242 * |  Code   | Glyph | Replacement |               Description              |
1243 * | ------- | ----- | ----------- | -------------------------------------- |
1244 * | U+00AA  | ª     | a           | Feminine ordinal indicator             |
1245 * | U+00BA  | º     | o           | Masculine ordinal indicator            |
1246 * | U+00C0  | À     | A           | Latin capital letter A with grave      |
1247 * | U+00C1  | Á     | A           | Latin capital letter A with acute      |
1248 * | U+00C2  |      | A           | Latin capital letter A with circumflex |
1249 * | U+00C3  | à    | A           | Latin capital letter A with tilde      |
1250 * | U+00C4  | Ä     | A           | Latin capital letter A with diaeresis  |
1251 * | U+00C5  | Å     | A           | Latin capital letter A with ring above |
1252 * | U+00C6  | Æ     | AE          | Latin capital letter AE                |
1253 * | U+00C7  | Ç     | C           | Latin capital letter C with cedilla    |
1254 * | U+00C8  | È     | E           | Latin capital letter E with grave      |
1255 * | U+00C9  | É     | E           | Latin capital letter E with acute      |
1256 * | U+00CA  | Ê     | E           | Latin capital letter E with circumflex |
1257 * | U+00CB  | Ë     | E           | Latin capital letter E with diaeresis  |
1258 * | U+00CC  | Ì     | I           | Latin capital letter I with grave      |
1259 * | U+00CD  | Í     | I           | Latin capital letter I with acute      |
1260 * | U+00CE  | Π    | I           | Latin capital letter I with circumflex |
1261 * | U+00CF  | Ï     | I           | Latin capital letter I with diaeresis  |
1262 * | U+00D0  | Р    | D           | Latin capital letter Eth               |
1263 * | U+00D1  | Ñ     | N           | Latin capital letter N with tilde      |
1264 * | U+00D2  | Ò     | O           | Latin capital letter O with grave      |
1265 * | U+00D3  | Ó     | O           | Latin capital letter O with acute      |
1266 * | U+00D4  | Ô     | O           | Latin capital letter O with circumflex |
1267 * | U+00D5  | Õ     | O           | Latin capital letter O with tilde      |
1268 * | U+00D6  | Ö     | O           | Latin capital letter O with diaeresis  |
1269 * | U+00D8  | Ø     | O           | Latin capital letter O with stroke     |
1270 * | U+00D9  | Ù     | U           | Latin capital letter U with grave      |
1271 * | U+00DA  | Ú     | U           | Latin capital letter U with acute      |
1272 * | U+00DB  | Û     | U           | Latin capital letter U with circumflex |
1273 * | U+00DC  | Ü     | U           | Latin capital letter U with diaeresis  |
1274 * | U+00DD  | Ý     | Y           | Latin capital letter Y with acute      |
1275 * | U+00DE  | Þ     | TH          | Latin capital letter Thorn             |
1276 * | U+00DF  | ß     | s           | Latin small letter sharp s             |
1277 * | U+00E0  | à     | a           | Latin small letter a with grave        |
1278 * | U+00E1  | á     | a           | Latin small letter a with acute        |
1279 * | U+00E2  | â     | a           | Latin small letter a with circumflex   |
1280 * | U+00E3  | ã     | a           | Latin small letter a with tilde        |
1281 * | U+00E4  | ä     | a           | Latin small letter a with diaeresis    |
1282 * | U+00E5  | å     | a           | Latin small letter a with ring above   |
1283 * | U+00E6  | æ     | ae          | Latin small letter ae                  |
1284 * | U+00E7  | ç     | c           | Latin small letter c with cedilla      |
1285 * | U+00E8  | è     | e           | Latin small letter e with grave        |
1286 * | U+00E9  | é     | e           | Latin small letter e with acute        |
1287 * | U+00EA  | ê     | e           | Latin small letter e with circumflex   |
1288 * | U+00EB  | ë     | e           | Latin small letter e with diaeresis    |
1289 * | U+00EC  | ì     | i           | Latin small letter i with grave        |
1290 * | U+00ED  | í     | i           | Latin small letter i with acute        |
1291 * | U+00EE  | î     | i           | Latin small letter i with circumflex   |
1292 * | U+00EF  | ï     | i           | Latin small letter i with diaeresis    |
1293 * | U+00F0  | ð     | d           | Latin small letter Eth                 |
1294 * | U+00F1  | ñ     | n           | Latin small letter n with tilde        |
1295 * | U+00F2  | ò     | o           | Latin small letter o with grave        |
1296 * | U+00F3  | ó     | o           | Latin small letter o with acute        |
1297 * | U+00F4  | ô     | o           | Latin small letter o with circumflex   |
1298 * | U+00F5  | õ     | o           | Latin small letter o with tilde        |
1299 * | U+00F6  | ö     | o           | Latin small letter o with diaeresis    |
1300 * | U+00F8  | ø     | o           | Latin small letter o with stroke       |
1301 * | U+00F9  | ù     | u           | Latin small letter u with grave        |
1302 * | U+00FA  | ú     | u           | Latin small letter u with acute        |
1303 * | U+00FB  | û     | u           | Latin small letter u with circumflex   |
1304 * | U+00FC  | ü     | u           | Latin small letter u with diaeresis    |
1305 * | U+00FD  | ý     | y           | Latin small letter y with acute        |
1306 * | U+00FE  | þ     | th          | Latin small letter Thorn               |
1307 * | U+00FF  | ÿ     | y           | Latin small letter y with diaeresis    |
1308 *
1309 * Decompositions for Latin Extended-A:
1310 *
1311 * |  Code   | Glyph | Replacement |                    Description                    |
1312 * | ------- | ----- | ----------- | ------------------------------------------------- |
1313 * | U+0100  | Ā     | A           | Latin capital letter A with macron                |
1314 * | U+0101  | ā     | a           | Latin small letter a with macron                  |
1315 * | U+0102  | Ă     | A           | Latin capital letter A with breve                 |
1316 * | U+0103  | ă     | a           | Latin small letter a with breve                   |
1317 * | U+0104  | Ą     | A           | Latin capital letter A with ogonek                |
1318 * | U+0105  | ą     | a           | Latin small letter a with ogonek                  |
1319 * | U+01006 | Ć     | C           | Latin capital letter C with acute                 |
1320 * | U+0107  | ć     | c           | Latin small letter c with acute                   |
1321 * | U+0108  | Ĉ     | C           | Latin capital letter C with circumflex            |
1322 * | U+0109  | ĉ     | c           | Latin small letter c with circumflex              |
1323 * | U+010A  | Ċ     | C           | Latin capital letter C with dot above             |
1324 * | U+010B  | ċ     | c           | Latin small letter c with dot above               |
1325 * | U+010C  | Č     | C           | Latin capital letter C with caron                 |
1326 * | U+010D  | č     | c           | Latin small letter c with caron                   |
1327 * | U+010E  | Ď     | D           | Latin capital letter D with caron                 |
1328 * | U+010F  | ď     | d           | Latin small letter d with caron                   |
1329 * | U+0110  | Đ     | D           | Latin capital letter D with stroke                |
1330 * | U+0111  | đ     | d           | Latin small letter d with stroke                  |
1331 * | U+0112  | Ē     | E           | Latin capital letter E with macron                |
1332 * | U+0113  | ē     | e           | Latin small letter e with macron                  |
1333 * | U+0114  | Ĕ     | E           | Latin capital letter E with breve                 |
1334 * | U+0115  | ĕ     | e           | Latin small letter e with breve                   |
1335 * | U+0116  | Ė     | E           | Latin capital letter E with dot above             |
1336 * | U+0117  | ė     | e           | Latin small letter e with dot above               |
1337 * | U+0118  | Ę     | E           | Latin capital letter E with ogonek                |
1338 * | U+0119  | ę     | e           | Latin small letter e with ogonek                  |
1339 * | U+011A  | Ě     | E           | Latin capital letter E with caron                 |
1340 * | U+011B  | ě     | e           | Latin small letter e with caron                   |
1341 * | U+011C  | Ĝ     | G           | Latin capital letter G with circumflex            |
1342 * | U+011D  | ĝ     | g           | Latin small letter g with circumflex              |
1343 * | U+011E  | Ğ     | G           | Latin capital letter G with breve                 |
1344 * | U+011F  | ğ     | g           | Latin small letter g with breve                   |
1345 * | U+0120  | Ġ     | G           | Latin capital letter G with dot above             |
1346 * | U+0121  | ġ     | g           | Latin small letter g with dot above               |
1347 * | U+0122  | Ģ     | G           | Latin capital letter G with cedilla               |
1348 * | U+0123  | ģ     | g           | Latin small letter g with cedilla                 |
1349 * | U+0124  | Ĥ     | H           | Latin capital letter H with circumflex            |
1350 * | U+0125  | ĥ     | h           | Latin small letter h with circumflex              |
1351 * | U+0126  | Ħ     | H           | Latin capital letter H with stroke                |
1352 * | U+0127  | ħ     | h           | Latin small letter h with stroke                  |
1353 * | U+0128  | Ĩ     | I           | Latin capital letter I with tilde                 |
1354 * | U+0129  | ĩ     | i           | Latin small letter i with tilde                   |
1355 * | U+012A  | Ī     | I           | Latin capital letter I with macron                |
1356 * | U+012B  | ī     | i           | Latin small letter i with macron                  |
1357 * | U+012C  | Ĭ     | I           | Latin capital letter I with breve                 |
1358 * | U+012D  | ĭ     | i           | Latin small letter i with breve                   |
1359 * | U+012E  | Į     | I           | Latin capital letter I with ogonek                |
1360 * | U+012F  | į     | i           | Latin small letter i with ogonek                  |
1361 * | U+0130  | İ     | I           | Latin capital letter I with dot above             |
1362 * | U+0131  | ı     | i           | Latin small letter dotless i                      |
1363 * | U+0132  | IJ     | IJ          | Latin capital ligature IJ                         |
1364 * | U+0133  | ij     | ij          | Latin small ligature ij                           |
1365 * | U+0134  | Ĵ     | J           | Latin capital letter J with circumflex            |
1366 * | U+0135  | ĵ     | j           | Latin small letter j with circumflex              |
1367 * | U+0136  | Ķ     | K           | Latin capital letter K with cedilla               |
1368 * | U+0137  | ķ     | k           | Latin small letter k with cedilla                 |
1369 * | U+0138  | ĸ     | k           | Latin small letter Kra                            |
1370 * | U+0139  | Ĺ     | L           | Latin capital letter L with acute                 |
1371 * | U+013A  | ĺ     | l           | Latin small letter l with acute                   |
1372 * | U+013B  | Ļ     | L           | Latin capital letter L with cedilla               |
1373 * | U+013C  | ļ     | l           | Latin small letter l with cedilla                 |
1374 * | U+013D  | Ľ     | L           | Latin capital letter L with caron                 |
1375 * | U+013E  | ľ     | l           | Latin small letter l with caron                   |
1376 * | U+013F  | Ŀ     | L           | Latin capital letter L with middle dot            |
1377 * | U+0140  | ŀ     | l           | Latin small letter l with middle dot              |
1378 * | U+0141  | Ł     | L           | Latin capital letter L with stroke                |
1379 * | U+0142  | ł     | l           | Latin small letter l with stroke                  |
1380 * | U+0143  | Ń     | N           | Latin capital letter N with acute                 |
1381 * | U+0144  | ń     | n           | Latin small letter N with acute                   |
1382 * | U+0145  | Ņ     | N           | Latin capital letter N with cedilla               |
1383 * | U+0146  | ņ     | n           | Latin small letter n with cedilla                 |
1384 * | U+0147  | Ň     | N           | Latin capital letter N with caron                 |
1385 * | U+0148  | ň     | n           | Latin small letter n with caron                   |
1386 * | U+0149  | ʼn     | n           | Latin small letter n preceded by apostrophe       |
1387 * | U+014A  | Ŋ     | N           | Latin capital letter Eng                          |
1388 * | U+014B  | ŋ     | n           | Latin small letter Eng                            |
1389 * | U+014C  | Ō     | O           | Latin capital letter O with macron                |
1390 * | U+014D  | ō     | o           | Latin small letter o with macron                  |
1391 * | U+014E  | Ŏ     | O           | Latin capital letter O with breve                 |
1392 * | U+014F  | ŏ     | o           | Latin small letter o with breve                   |
1393 * | U+0150  | Ő     | O           | Latin capital letter O with double acute          |
1394 * | U+0151  | ő     | o           | Latin small letter o with double acute            |
1395 * | U+0152  | Œ     | OE          | Latin capital ligature OE                         |
1396 * | U+0153  | œ     | oe          | Latin small ligature oe                           |
1397 * | U+0154  | Ŕ     | R           | Latin capital letter R with acute                 |
1398 * | U+0155  | ŕ     | r           | Latin small letter r with acute                   |
1399 * | U+0156  | Ŗ     | R           | Latin capital letter R with cedilla               |
1400 * | U+0157  | ŗ     | r           | Latin small letter r with cedilla                 |
1401 * | U+0158  | Ř     | R           | Latin capital letter R with caron                 |
1402 * | U+0159  | ř     | r           | Latin small letter r with caron                   |
1403 * | U+015A  | Ś     | S           | Latin capital letter S with acute                 |
1404 * | U+015B  | ś     | s           | Latin small letter s with acute                   |
1405 * | U+015C  | Ŝ     | S           | Latin capital letter S with circumflex            |
1406 * | U+015D  | ŝ     | s           | Latin small letter s with circumflex              |
1407 * | U+015E  | Ş     | S           | Latin capital letter S with cedilla               |
1408 * | U+015F  | ş     | s           | Latin small letter s with cedilla                 |
1409 * | U+0160  | Š     | S           | Latin capital letter S with caron                 |
1410 * | U+0161  | š     | s           | Latin small letter s with caron                   |
1411 * | U+0162  | Ţ     | T           | Latin capital letter T with cedilla               |
1412 * | U+0163  | ţ     | t           | Latin small letter t with cedilla                 |
1413 * | U+0164  | Ť     | T           | Latin capital letter T with caron                 |
1414 * | U+0165  | ť     | t           | Latin small letter t with caron                   |
1415 * | U+0166  | Ŧ     | T           | Latin capital letter T with stroke                |
1416 * | U+0167  | ŧ     | t           | Latin small letter t with stroke                  |
1417 * | U+0168  | Ũ     | U           | Latin capital letter U with tilde                 |
1418 * | U+0169  | ũ     | u           | Latin small letter u with tilde                   |
1419 * | U+016A  | Ū     | U           | Latin capital letter U with macron                |
1420 * | U+016B  | ū     | u           | Latin small letter u with macron                  |
1421 * | U+016C  | Ŭ     | U           | Latin capital letter U with breve                 |
1422 * | U+016D  | ŭ     | u           | Latin small letter u with breve                   |
1423 * | U+016E  | Ů     | U           | Latin capital letter U with ring above            |
1424 * | U+016F  | ů     | u           | Latin small letter u with ring above              |
1425 * | U+0170  | Ű     | U           | Latin capital letter U with double acute          |
1426 * | U+0171  | ű     | u           | Latin small letter u with double acute            |
1427 * | U+0172  | Ų     | U           | Latin capital letter U with ogonek                |
1428 * | U+0173  | ų     | u           | Latin small letter u with ogonek                  |
1429 * | U+0174  | Ŵ     | W           | Latin capital letter W with circumflex            |
1430 * | U+0175  | ŵ     | w           | Latin small letter w with circumflex              |
1431 * | U+0176  | Ŷ     | Y           | Latin capital letter Y with circumflex            |
1432 * | U+0177  | ŷ     | y           | Latin small letter y with circumflex              |
1433 * | U+0178  | Ÿ     | Y           | Latin capital letter Y with diaeresis             |
1434 * | U+0179  | Ź     | Z           | Latin capital letter Z with acute                 |
1435 * | U+017A  | ź     | z           | Latin small letter z with acute                   |
1436 * | U+017B  | Ż     | Z           | Latin capital letter Z with dot above             |
1437 * | U+017C  | ż     | z           | Latin small letter z with dot above               |
1438 * | U+017D  | Ž     | Z           | Latin capital letter Z with caron                 |
1439 * | U+017E  | ž     | z           | Latin small letter z with caron                   |
1440 * | U+017F  | ſ     | s           | Latin small letter long s                         |
1441 * | U+01A0  | Ơ     | O           | Latin capital letter O with horn                  |
1442 * | U+01A1  | ơ     | o           | Latin small letter o with horn                    |
1443 * | U+01AF  | Ư     | U           | Latin capital letter U with horn                  |
1444 * | U+01B0  | ư     | u           | Latin small letter u with horn                    |
1445 * | U+01CD  | Ǎ     | A           | Latin capital letter A with caron                 |
1446 * | U+01CE  | ǎ     | a           | Latin small letter a with caron                   |
1447 * | U+01CF  | Ǐ     | I           | Latin capital letter I with caron                 |
1448 * | U+01D0  | ǐ     | i           | Latin small letter i with caron                   |
1449 * | U+01D1  | Ǒ     | O           | Latin capital letter O with caron                 |
1450 * | U+01D2  | ǒ     | o           | Latin small letter o with caron                   |
1451 * | U+01D3  | Ǔ     | U           | Latin capital letter U with caron                 |
1452 * | U+01D4  | ǔ     | u           | Latin small letter u with caron                   |
1453 * | U+01D5  | Ǖ     | U           | Latin capital letter U with diaeresis and macron  |
1454 * | U+01D6  | ǖ     | u           | Latin small letter u with diaeresis and macron    |
1455 * | U+01D7  | Ǘ     | U           | Latin capital letter U with diaeresis and acute   |
1456 * | U+01D8  | ǘ     | u           | Latin small letter u with diaeresis and acute     |
1457 * | U+01D9  | Ǚ     | U           | Latin capital letter U with diaeresis and caron   |
1458 * | U+01DA  | ǚ     | u           | Latin small letter u with diaeresis and caron     |
1459 * | U+01DB  | Ǜ     | U           | Latin capital letter U with diaeresis and grave   |
1460 * | U+01DC  | ǜ     | u           | Latin small letter u with diaeresis and grave     |
1461 *
1462 * Decompositions for Latin Extended-B:
1463 *
1464 * |   Code   | Glyph | Replacement |                Description                |
1465 * | -------- | ----- | ----------- | ----------------------------------------- |
1466 * | U+0218   | Ș     | S           | Latin capital letter S with comma below   |
1467 * | U+0219   | ș     | s           | Latin small letter s with comma below     |
1468 * | U+021A   | Ț     | T           | Latin capital letter T with comma below   |
1469 * | U+021B   | ț     | t           | Latin small letter t with comma below     |
1470 *
1471 * Vowels with diacritic (Chinese, Hanyu Pinyin):
1472 *
1473 * |   Code   | Glyph | Replacement |                      Description                      |
1474 * | -------- | ----- | ----------- | ----------------------------------------------------- |
1475 * | U+0251   | ɑ     | a           | Latin small letter alpha                              |
1476 * | U+1EA0   | Ạ     | A           | Latin capital letter A with dot below                 |
1477 * | U+1EA1   | ạ     | a           | Latin small letter a with dot below                   |
1478 * | U+1EA2   | Ả     | A           | Latin capital letter A with hook above                |
1479 * | U+1EA3   | ả     | a           | Latin small letter a with hook above                  |
1480 * | U+1EA4   | Ấ     | A           | Latin capital letter A with circumflex and acute      |
1481 * | U+1EA5   | ấ     | a           | Latin small letter a with circumflex and acute        |
1482 * | U+1EA6   | Ầ     | A           | Latin capital letter A with circumflex and grave      |
1483 * | U+1EA7   | ầ     | a           | Latin small letter a with circumflex and grave        |
1484 * | U+1EA8   | Ẩ     | A           | Latin capital letter A with circumflex and hook above |
1485 * | U+1EA9   | ẩ     | a           | Latin small letter a with circumflex and hook above   |
1486 * | U+1EAA   | Ẫ     | A           | Latin capital letter A with circumflex and tilde      |
1487 * | U+1EAB   | ẫ     | a           | Latin small letter a with circumflex and tilde        |
1488 * | U+1EA6   | Ậ     | A           | Latin capital letter A with circumflex and dot below  |
1489 * | U+1EAD   | ậ     | a           | Latin small letter a with circumflex and dot below    |
1490 * | U+1EAE   | Ắ     | A           | Latin capital letter A with breve and acute           |
1491 * | U+1EAF   | ắ     | a           | Latin small letter a with breve and acute             |
1492 * | U+1EB0   | Ằ     | A           | Latin capital letter A with breve and grave           |
1493 * | U+1EB1   | ằ     | a           | Latin small letter a with breve and grave             |
1494 * | U+1EB2   | Ẳ     | A           | Latin capital letter A with breve and hook above      |
1495 * | U+1EB3   | ẳ     | a           | Latin small letter a with breve and hook above        |
1496 * | U+1EB4   | Ẵ     | A           | Latin capital letter A with breve and tilde           |
1497 * | U+1EB5   | ẵ     | a           | Latin small letter a with breve and tilde             |
1498 * | U+1EB6   | Ặ     | A           | Latin capital letter A with breve and dot below       |
1499 * | U+1EB7   | ặ     | a           | Latin small letter a with breve and dot below         |
1500 * | U+1EB8   | Ẹ     | E           | Latin capital letter E with dot below                 |
1501 * | U+1EB9   | ẹ     | e           | Latin small letter e with dot below                   |
1502 * | U+1EBA   | Ẻ     | E           | Latin capital letter E with hook above                |
1503 * | U+1EBB   | ẻ     | e           | Latin small letter e with hook above                  |
1504 * | U+1EBC   | Ẽ     | E           | Latin capital letter E with tilde                     |
1505 * | U+1EBD   | ẽ     | e           | Latin small letter e with tilde                       |
1506 * | U+1EBE   | Ế     | E           | Latin capital letter E with circumflex and acute      |
1507 * | U+1EBF   | ế     | e           | Latin small letter e with circumflex and acute        |
1508 * | U+1EC0   | Ề     | E           | Latin capital letter E with circumflex and grave      |
1509 * | U+1EC1   | ề     | e           | Latin small letter e with circumflex and grave        |
1510 * | U+1EC2   | Ể     | E           | Latin capital letter E with circumflex and hook above |
1511 * | U+1EC3   | ể     | e           | Latin small letter e with circumflex and hook above   |
1512 * | U+1EC4   | Ễ     | E           | Latin capital letter E with circumflex and tilde      |
1513 * | U+1EC5   | ễ     | e           | Latin small letter e with circumflex and tilde        |
1514 * | U+1EC6   | Ệ     | E           | Latin capital letter E with circumflex and dot below  |
1515 * | U+1EC7   | ệ     | e           | Latin small letter e with circumflex and dot below    |
1516 * | U+1EC8   | Ỉ     | I           | Latin capital letter I with hook above                |
1517 * | U+1EC9   | ỉ     | i           | Latin small letter i with hook above                  |
1518 * | U+1ECA   | Ị     | I           | Latin capital letter I with dot below                 |
1519 * | U+1ECB   | ị     | i           | Latin small letter i with dot below                   |
1520 * | U+1ECC   | Ọ     | O           | Latin capital letter O with dot below                 |
1521 * | U+1ECD   | ọ     | o           | Latin small letter o with dot below                   |
1522 * | U+1ECE   | Ỏ     | O           | Latin capital letter O with hook above                |
1523 * | U+1ECF   | ỏ     | o           | Latin small letter o with hook above                  |
1524 * | U+1ED0   | Ố     | O           | Latin capital letter O with circumflex and acute      |
1525 * | U+1ED1   | ố     | o           | Latin small letter o with circumflex and acute        |
1526 * | U+1ED2   | Ồ     | O           | Latin capital letter O with circumflex and grave      |
1527 * | U+1ED3   | ồ     | o           | Latin small letter o with circumflex and grave        |
1528 * | U+1ED4   | Ổ     | O           | Latin capital letter O with circumflex and hook above |
1529 * | U+1ED5   | ổ     | o           | Latin small letter o with circumflex and hook above   |
1530 * | U+1ED6   | Ỗ     | O           | Latin capital letter O with circumflex and tilde      |
1531 * | U+1ED7   | ỗ     | o           | Latin small letter o with circumflex and tilde        |
1532 * | U+1ED8   | Ộ     | O           | Latin capital letter O with circumflex and dot below  |
1533 * | U+1ED9   | ộ     | o           | Latin small letter o with circumflex and dot below    |
1534 * | U+1EDA   | Ớ     | O           | Latin capital letter O with horn and acute            |
1535 * | U+1EDB   | ớ     | o           | Latin small letter o with horn and acute              |
1536 * | U+1EDC   | Ờ     | O           | Latin capital letter O with horn and grave            |
1537 * | U+1EDD   | ờ     | o           | Latin small letter o with horn and grave              |
1538 * | U+1EDE   | Ở     | O           | Latin capital letter O with horn and hook above       |
1539 * | U+1EDF   | ở     | o           | Latin small letter o with horn and hook above         |
1540 * | U+1EE0   | Ỡ     | O           | Latin capital letter O with horn and tilde            |
1541 * | U+1EE1   | ỡ     | o           | Latin small letter o with horn and tilde              |
1542 * | U+1EE2   | Ợ     | O           | Latin capital letter O with horn and dot below        |
1543 * | U+1EE3   | ợ     | o           | Latin small letter o with horn and dot below          |
1544 * | U+1EE4   | Ụ     | U           | Latin capital letter U with dot below                 |
1545 * | U+1EE5   | ụ     | u           | Latin small letter u with dot below                   |
1546 * | U+1EE6   | Ủ     | U           | Latin capital letter U with hook above                |
1547 * | U+1EE7   | ủ     | u           | Latin small letter u with hook above                  |
1548 * | U+1EE8   | Ứ     | U           | Latin capital letter U with horn and acute            |
1549 * | U+1EE9   | ứ     | u           | Latin small letter u with horn and acute              |
1550 * | U+1EEA   | Ừ     | U           | Latin capital letter U with horn and grave            |
1551 * | U+1EEB   | ừ     | u           | Latin small letter u with horn and grave              |
1552 * | U+1EEC   | Ử     | U           | Latin capital letter U with horn and hook above       |
1553 * | U+1EED   | ử     | u           | Latin small letter u with horn and hook above         |
1554 * | U+1EEE   | Ữ     | U           | Latin capital letter U with horn and tilde            |
1555 * | U+1EEF   | ữ     | u           | Latin small letter u with horn and tilde              |
1556 * | U+1EF0   | Ự     | U           | Latin capital letter U with horn and dot below        |
1557 * | U+1EF1   | ự     | u           | Latin small letter u with horn and dot below          |
1558 * | U+1EF2   | Ỳ     | Y           | Latin capital letter Y with grave                     |
1559 * | U+1EF3   | ỳ     | y           | Latin small letter y with grave                       |
1560 * | U+1EF4   | Ỵ     | Y           | Latin capital letter Y with dot below                 |
1561 * | U+1EF5   | ỵ     | y           | Latin small letter y with dot below                   |
1562 * | U+1EF6   | Ỷ     | Y           | Latin capital letter Y with hook above                |
1563 * | U+1EF7   | ỷ     | y           | Latin small letter y with hook above                  |
1564 * | U+1EF8   | Ỹ     | Y           | Latin capital letter Y with tilde                     |
1565 * | U+1EF9   | ỹ     | y           | Latin small letter y with tilde                       |
1566 *
1567 * German (`de_DE`), German formal (`de_DE_formal`), German (Switzerland) formal (`de_CH`),
1568 * and German (Switzerland) informal (`de_CH_informal`) locales:
1569 *
1570 * |   Code   | Glyph | Replacement |               Description               |
1571 * | -------- | ----- | ----------- | --------------------------------------- |
1572 * | U+00C4   | Ä     | Ae          | Latin capital letter A with diaeresis   |
1573 * | U+00E4   | ä     | ae          | Latin small letter a with diaeresis     |
1574 * | U+00D6   | Ö     | Oe          | Latin capital letter O with diaeresis   |
1575 * | U+00F6   | ö     | oe          | Latin small letter o with diaeresis     |
1576 * | U+00DC   | Ü     | Ue          | Latin capital letter U with diaeresis   |
1577 * | U+00FC   | ü     | ue          | Latin small letter u with diaeresis     |
1578 * | U+00DF   | ß     | ss          | Latin small letter sharp s              |
1579 *
1580 * Danish (`da_DK`) locale:
1581 *
1582 * |   Code   | Glyph | Replacement |               Description               |
1583 * | -------- | ----- | ----------- | --------------------------------------- |
1584 * | U+00C6   | Æ     | Ae          | Latin capital letter AE                 |
1585 * | U+00E6   | æ     | ae          | Latin small letter ae                   |
1586 * | U+00D8   | Ø     | Oe          | Latin capital letter O with stroke      |
1587 * | U+00F8   | ø     | oe          | Latin small letter o with stroke        |
1588 * | U+00C5   | Å     | Aa          | Latin capital letter A with ring above  |
1589 * | U+00E5   | å     | aa          | Latin small letter a with ring above    |
1590 *
1591 * Catalan (`ca`) locale:
1592 *
1593 * |   Code   | Glyph | Replacement |               Description               |
1594 * | -------- | ----- | ----------- | --------------------------------------- |
1595 * | U+00B7   | l·l   | ll          | Flown dot (between two Ls)              |
1596 *
1597 * Serbian (`sr_RS`) and Bosnian (`bs_BA`) locales:
1598 *
1599 * |   Code   | Glyph | Replacement |               Description               |
1600 * | -------- | ----- | ----------- | --------------------------------------- |
1601 * | U+0110   | Đ     | DJ          | Latin capital letter D with stroke      |
1602 * | U+0111   | đ     | dj          | Latin small letter d with stroke        |
1603 *
1604 * @since 1.2.1
1605 * @since 4.6.0 Added locale support for `de_CH`, `de_CH_informal`, and `ca`.
1606 * @since 4.7.0 Added locale support for `sr_RS`.
1607 * @since 4.8.0 Added locale support for `bs_BA`.
1608 *
1609 * @param string $string Text that might have accent characters
1610 * @return string Filtered string with replaced "nice" characters.
1611 */
1612function remove_accents($string)
1613{
1614    if (! preg_match('/[\x80-\xff]/', $string)) {
1615        return $string;
1616    }
1617
1618    if (seems_utf8($string)) {
1619        $chars = array(
1620            // Decompositions for Latin-1 Supplement
1621            'ª' => 'a',
1622            'º' => 'o',
1623            'À' => 'A',
1624            'Á' => 'A',
1625            'Â' => 'A',
1626            'Ã' => 'A',
1627            'Ä' => 'A',
1628            'Å' => 'A',
1629            'Æ' => 'AE',
1630            'Ç' => 'C',
1631            'È' => 'E',
1632            'É' => 'E',
1633            'Ê' => 'E',
1634            'Ë' => 'E',
1635            'Ì' => 'I',
1636            'Í' => 'I',
1637            'Î' => 'I',
1638            'Ï' => 'I',
1639            'Ð' => 'D',
1640            'Ñ' => 'N',
1641            'Ò' => 'O',
1642            'Ó' => 'O',
1643            'Ô' => 'O',
1644            'Õ' => 'O',
1645            'Ö' => 'O',
1646            'Ù' => 'U',
1647            'Ú' => 'U',
1648            'Û' => 'U',
1649            'Ü' => 'U',
1650            'Ý' => 'Y',
1651            'Þ' => 'TH',
1652            'ß' => 's',
1653            'à' => 'a',
1654            'á' => 'a',
1655            'â' => 'a',
1656            'ã' => 'a',
1657            'ä' => 'a',
1658            'å' => 'a',
1659            'æ' => 'ae',
1660            'ç' => 'c',
1661            'è' => 'e',
1662            'é' => 'e',
1663            'ê' => 'e',
1664            'ë' => 'e',
1665            'ì' => 'i',
1666            'í' => 'i',
1667            'î' => 'i',
1668            'ï' => 'i',
1669            'ð' => 'd',
1670            'ñ' => 'n',
1671            'ò' => 'o',
1672            'ó' => 'o',
1673            'ô' => 'o',
1674            'õ' => 'o',
1675            'ö' => 'o',
1676            'ø' => 'o',
1677            'ù' => 'u',
1678            'ú' => 'u',
1679            'û' => 'u',
1680            'ü' => 'u',
1681            'ý' => 'y',
1682            'þ' => 'th',
1683            'ÿ' => 'y',
1684            'Ø' => 'O',
1685            // Decompositions for Latin Extended-A
1686            'Ā' => 'A',
1687            'ā' => 'a',
1688            'Ă' => 'A',
1689            'ă' => 'a',
1690            'Ą' => 'A',
1691            'ą' => 'a',
1692            'Ć' => 'C',
1693            'ć' => 'c',
1694            'Ĉ' => 'C',
1695            'ĉ' => 'c',
1696            'Ċ' => 'C',
1697            'ċ' => 'c',
1698            'Č' => 'C',
1699            'č' => 'c',
1700            'Ď' => 'D',
1701            'ď' => 'd',
1702            'Đ' => 'D',
1703            'đ' => 'd',
1704            'Ē' => 'E',
1705            'ē' => 'e',
1706            'Ĕ' => 'E',
1707            'ĕ' => 'e',
1708            'Ė' => 'E',
1709            'ė' => 'e',
1710            'Ę' => 'E',
1711            'ę' => 'e',
1712            'Ě' => 'E',
1713            'ě' => 'e',
1714            'Ĝ' => 'G',
1715            'ĝ' => 'g',
1716            'Ğ' => 'G',
1717            'ğ' => 'g',
1718            'Ġ' => 'G',
1719            'ġ' => 'g',
1720            'Ģ' => 'G',
1721            'ģ' => 'g',
1722            'Ĥ' => 'H',
1723            'ĥ' => 'h',
1724            'Ħ' => 'H',
1725            'ħ' => 'h',
1726            'Ĩ' => 'I',
1727            'ĩ' => 'i',
1728            'Ī' => 'I',
1729            'ī' => 'i',
1730            'Ĭ' => 'I',
1731            'ĭ' => 'i',
1732            'Į' => 'I',
1733            'į' => 'i',
1734            'İ' => 'I',
1735            'ı' => 'i',
1736            'IJ' => 'IJ',
1737            'ij' => 'ij',
1738            'Ĵ' => 'J',
1739            'ĵ' => 'j',
1740            'Ķ' => 'K',
1741            'ķ' => 'k',
1742            'ĸ' => 'k',
1743            'Ĺ' => 'L',
1744            'ĺ' => 'l',
1745            'Ļ' => 'L',
1746            'ļ' => 'l',
1747            'Ľ' => 'L',
1748            'ľ' => 'l',
1749            'Ŀ' => 'L',
1750            'ŀ' => 'l',
1751            'Ł' => 'L',
1752            'ł' => 'l',
1753            'Ń' => 'N',
1754            'ń' => 'n',
1755            'Ņ' => 'N',
1756            'ņ' => 'n',
1757            'Ň' => 'N',
1758            'ň' => 'n',
1759            'ʼn' => 'n',
1760            'Ŋ' => 'N',
1761            'ŋ' => 'n',
1762            'Ō' => 'O',
1763            'ō' => 'o',
1764            'Ŏ' => 'O',
1765            'ŏ' => 'o',
1766            'Ő' => 'O',
1767            'ő' => 'o',
1768            'Œ' => 'OE',
1769            'œ' => 'oe',
1770            'Ŕ' => 'R',
1771            'ŕ' => 'r',
1772            'Ŗ' => 'R',
1773            'ŗ' => 'r',
1774            'Ř' => 'R',
1775            'ř' => 'r',
1776            'Ś' => 'S',
1777            'ś' => 's',
1778            'Ŝ' => 'S',
1779            'ŝ' => 's',
1780            'Ş' => 'S',
1781            'ş' => 's',
1782            'Š' => 'S',
1783            'š' => 's',
1784            'Ţ' => 'T',
1785            'ţ' => 't',
1786            'Ť' => 'T',
1787            'ť' => 't',
1788            'Ŧ' => 'T',
1789            'ŧ' => 't',
1790            'Ũ' => 'U',
1791            'ũ' => 'u',
1792            'Ū' => 'U',
1793            'ū' => 'u',
1794            'Ŭ' => 'U',
1795            'ŭ' => 'u',
1796            'Ů' => 'U',
1797            'ů' => 'u',
1798            'Ű' => 'U',
1799            'ű' => 'u',
1800            'Ų' => 'U',
1801            'ų' => 'u',
1802            'Ŵ' => 'W',
1803            'ŵ' => 'w',
1804            'Ŷ' => 'Y',
1805            'ŷ' => 'y',
1806            'Ÿ' => 'Y',
1807            'Ź' => 'Z',
1808            'ź' => 'z',
1809            'Ż' => 'Z',
1810            'ż' => 'z',
1811            'Ž' => 'Z',
1812            'ž' => 'z',
1813            'ſ' => 's',
1814            // Decompositions for Latin Extended-B
1815            'Ș' => 'S',
1816            'ș' => 's',
1817            'Ț' => 'T',
1818            'ț' => 't',
1819            // Euro Sign
1820            '€' => 'E',
1821            // GBP (Pound) Sign
1822            '£' => '',
1823            // Vowels with diacritic (Vietnamese)
1824            // unmarked
1825            'Ơ' => 'O',
1826            'ơ' => 'o',
1827            'Ư' => 'U',
1828            'ư' => 'u',
1829            // grave accent
1830            'Ầ' => 'A',
1831            'ầ' => 'a',
1832            'Ằ' => 'A',
1833            'ằ' => 'a',
1834            'Ề' => 'E',
1835            'ề' => 'e',
1836            'Ồ' => 'O',
1837            'ồ' => 'o',
1838            'Ờ' => 'O',
1839            'ờ' => 'o',
1840            'Ừ' => 'U',
1841            'ừ' => 'u',
1842            'Ỳ' => 'Y',
1843            'ỳ' => 'y',
1844            // hook
1845            'Ả' => 'A',
1846            'ả' => 'a',
1847            'Ẩ' => 'A',
1848            'ẩ' => 'a',
1849            'Ẳ' => 'A',
1850            'ẳ' => 'a',
1851            'Ẻ' => 'E',
1852            'ẻ' => 'e',
1853            'Ể' => 'E',
1854            'ể' => 'e',
1855            'Ỉ' => 'I',
1856            'ỉ' => 'i',
1857            'Ỏ' => 'O',
1858            'ỏ' => 'o',
1859            'Ổ' => 'O',
1860            'ổ' => 'o',
1861            'Ở' => 'O',
1862            'ở' => 'o',
1863            'Ủ' => 'U',
1864            'ủ' => 'u',
1865            'Ử' => 'U',
1866            'ử' => 'u',
1867            'Ỷ' => 'Y',
1868            'ỷ' => 'y',
1869            // tilde
1870            'Ẫ' => 'A',
1871            'ẫ' => 'a',
1872            'Ẵ' => 'A',
1873            'ẵ' => 'a',
1874            'Ẽ' => 'E',
1875            'ẽ' => 'e',
1876            'Ễ' => 'E',
1877            'ễ' => 'e',
1878            'Ỗ' => 'O',
1879            'ỗ' => 'o',
1880            'Ỡ' => 'O',
1881            'ỡ' => 'o',
1882            'Ữ' => 'U',
1883            'ữ' => 'u',
1884            'Ỹ' => 'Y',
1885            'ỹ' => 'y',
1886            // acute accent
1887            'Ấ' => 'A',
1888            'ấ' => 'a',
1889            'Ắ' => 'A',
1890            'ắ' => 'a',
1891            'Ế' => 'E',
1892            'ế' => 'e',
1893            'Ố' => 'O',
1894            'ố' => 'o',
1895            'Ớ' => 'O',
1896            'ớ' => 'o',
1897            'Ứ' => 'U',
1898            'ứ' => 'u',
1899            // dot below
1900            'Ạ' => 'A',
1901            'ạ' => 'a',
1902            'Ậ' => 'A',
1903            'ậ' => 'a',
1904            'Ặ' => 'A',
1905            'ặ' => 'a',
1906            'Ẹ' => 'E',
1907            'ẹ' => 'e',
1908            'Ệ' => 'E',
1909            'ệ' => 'e',
1910            'Ị' => 'I',
1911            'ị' => 'i',
1912            'Ọ' => 'O',
1913            'ọ' => 'o',
1914            'Ộ' => 'O',
1915            'ộ' => 'o',
1916            'Ợ' => 'O',
1917            'ợ' => 'o',
1918            'Ụ' => 'U',
1919            'ụ' => 'u',
1920            'Ự' => 'U',
1921            'ự' => 'u',
1922            'Ỵ' => 'Y',
1923            'ỵ' => 'y',
1924            // Vowels with diacritic (Chinese, Hanyu Pinyin)
1925            'ɑ' => 'a',
1926            // macron
1927            'Ǖ' => 'U',
1928            'ǖ' => 'u',
1929            // acute accent
1930            'Ǘ' => 'U',
1931            'ǘ' => 'u',
1932            // caron
1933            'Ǎ' => 'A',
1934            'ǎ' => 'a',
1935            'Ǐ' => 'I',
1936            'ǐ' => 'i',
1937            'Ǒ' => 'O',
1938            'ǒ' => 'o',
1939            'Ǔ' => 'U',
1940            'ǔ' => 'u',
1941            'Ǚ' => 'U',
1942            'ǚ' => 'u',
1943            // grave accent
1944            'Ǜ' => 'U',
1945            'ǜ' => 'u',
1946        );
1947
1948        // Used for locale-specific rules
1949        $locale = get_locale();
1950
1951        if ('de_DE' == $locale || 'de_DE_formal' == $locale || 'de_CH' == $locale || 'de_CH_informal' == $locale) {
1952            $chars['Ä'] = 'Ae';
1953            $chars['ä'] = 'ae';
1954            $chars['Ö'] = 'Oe';
1955            $chars['ö'] = 'oe';
1956            $chars['Ü'] = 'Ue';
1957            $chars['ü'] = 'ue';
1958            $chars['ß'] = 'ss';
1959        } elseif ('da_DK' === $locale) {
1960            $chars['Æ'] = 'Ae';
1961            $chars['æ'] = 'ae';
1962            $chars['Ø'] = 'Oe';
1963            $chars['ø'] = 'oe';
1964            $chars['Å'] = 'Aa';
1965            $chars['å'] = 'aa';
1966        } elseif ('ca' === $locale) {
1967            $chars['l·l'] = 'll';
1968        } elseif ('sr_RS' === $locale || 'bs_BA' === $locale) {
1969            $chars['Đ'] = 'DJ';
1970            $chars['đ'] = 'dj';
1971        }
1972
1973        $string = strtr($string, $chars);
1974    } else {
1975        $chars = array();
1976        // Assume ISO-8859-1 if not UTF-8
1977        $chars['in'] = "\x80\x83\x8a\x8e\x9a\x9e"
1978            . "\x9f\xa2\xa5\xb5\xc0\xc1\xc2"
1979            . "\xc3\xc4\xc5\xc7\xc8\xc9\xca"
1980            . "\xcb\xcc\xcd\xce\xcf\xd1\xd2"
1981            . "\xd3\xd4\xd5\xd6\xd8\xd9\xda"
1982            . "\xdb\xdc\xdd\xe0\xe1\xe2\xe3"
1983            . "\xe4\xe5\xe7\xe8\xe9\xea\xeb"
1984            . "\xec\xed\xee\xef\xf1\xf2\xf3"
1985            . "\xf4\xf5\xf6\xf8\xf9\xfa\xfb"
1986            . "\xfc\xfd\xff";
1987
1988        $chars['out'] = 'EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy';
1989
1990        $string              = strtr($string, $chars['in'], $chars['out']);
1991        $double_chars        = array();
1992        $double_chars['in']  = array( "\x8c", "\x9c", "\xc6", "\xd0", "\xde", "\xdf", "\xe6", "\xf0", "\xfe" );
1993        $double_chars['out'] = array( 'OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th' );
1994        $string              = str_replace($double_chars['in'], $double_chars['out'], $string);
1995    }
1996
1997    return $string;
1998}
1999
2000/**
2001 * Sanitizes a filename, replacing whitespace with dashes.
2002 *
2003 * Removes special characters that are illegal in filenames on certain
2004 * operating systems and special characters requiring special escaping
2005 * to manipulate at the command line. Replaces spaces and consecutive
2006 * dashes with a single dash. Trims period, dash and underscore from beginning
2007 * and end of filename. It is not guaranteed that this function will return a
2008 * filename that is allowed to be uploaded.
2009 *
2010 * @since 2.1.0
2011 *
2012 * @param string $filename The filename to be sanitized
2013 * @return string The sanitized filename
2014 */
2015function sanitize_file_name($filename)
2016{
2017    $filename_raw  = $filename;
2018    $special_chars = array( '?', '[', ']', '/', '\\', '=', '<', '>', ':', ';', ',', "'", '"', '&', '$', '#', '*', '(', ')', '|', '~', '`', '!', '{', '}', '%', '+', chr(0) );
2019    /**
2020     * Filters the list of characters to remove from a filename.
2021     *
2022     * @since 2.8.0
2023     *
2024     * @param array  $special_chars Characters to remove.
2025     * @param string $filename_raw  Filename as it was passed into sanitize_file_name().
2026     */
2027    $special_chars = apply_filters('sanitize_file_name_chars', $special_chars, $filename_raw);
2028    $filename      = preg_replace("#\x{00a0}#siu", ' ', $filename);
2029    $filename      = str_replace($special_chars, '', $filename);
2030    $filename      = str_replace(array( '%20', '+' ), '-', $filename);
2031    $filename      = preg_replace('/[\r\n\t -]+/', '-', $filename);
2032    $filename      = trim($filename, '.-_');
2033
2034    if (false === strpos($filename, '.')) {
2035        $mime_types = wp_get_mime_types();
2036        $filetype   = wp_check_filetype('test.' . $filename, $mime_types);
2037        if ($filetype['ext'] === $filename) {
2038            $filename = 'unnamed-file.' . $filetype['ext'];
2039        }
2040    }
2041
2042    // Split the filename into a base and extension[s]
2043    $parts = explode('.', $filename);
2044
2045    // Return if only one extension
2046    if (count($parts) <= 2) {
2047        /**
2048         * Filters a sanitized filename string.
2049         *
2050         * @since 2.8.0
2051         *
2052         * @param string $filename     Sanitized filename.
2053         * @param string $filename_raw The filename prior to sanitization.
2054         */
2055        return apply_filters('sanitize_file_name', $filename, $filename_raw);
2056    }
2057
2058    // Process multiple extensions
2059    $filename  = array_shift($parts);
2060    $extension = array_pop($parts);
2061    $mimes     = get_allowed_mime_types();
2062
2063    /*
2064     * Loop over any intermediate extensions. Postfix them with a trailing underscore
2065     * if they are a 2 - 5 character long alpha string not in the extension whitelist.
2066     */
2067    foreach ((array) $parts as $part) {
2068        $filename .= '.' . $part;
2069
2070        if (preg_match('/^[a-zA-Z]{2,5}\d?$/', $part)) {
2071            $allowed = false;
2072            foreach ($mimes as $ext_preg => $mime_match) {
2073                $ext_preg = '!^(' . $ext_preg . ')$!i';
2074                if (preg_match($ext_preg, $part)) {
2075                    $allowed = true;
2076                    break;
2077                }
2078            }
2079            if (! $allowed) {
2080                $filename .= '_';
2081            }
2082        }
2083    }
2084    $filename .= '.' . $extension;
2085    /** This filter is documented in wp-includes/formatting.php */
2086    return apply_filters('sanitize_file_name', $filename, $filename_raw);
2087}
2088
2089/**
2090 * Sanitizes a username, stripping out unsafe characters.
2091 *
2092 * Removes tags, octets, entities, and if strict is enabled, will only keep
2093 * alphanumeric, _, space, ., -, @. After sanitizing, it passes the username,
2094 * raw username (the username in the parameter), and the value of $strict as
2095 * parameters for the {@see 'sanitize_user'} filter.
2096 *
2097 * @since 2.0.0
2098 *
2099 * @param string $username The username to be sanitized.
2100 * @param bool   $strict   If set limits $username to specific characters. Default false.
2101 * @return string The sanitized username, after passing through filters.
2102 */
2103function sanitize_user($username, $strict = false)
2104{
2105    $raw_username = $username;
2106    $username     = wp_strip_all_tags($username);
2107    $username     = remove_accents($username);
2108    // Kill octets
2109    $username = preg_replace('|%([a-fA-F0-9][a-fA-F0-9])|', '', $username);
2110    $username = preg_replace('/&.+?;/', '', $username); // Kill entities
2111
2112    // If strict, reduce to ASCII for max portability.
2113    if ($strict) {
2114        $username = preg_replace('|[^a-z0-9 _.\-@]|i', '', $username);
2115    }
2116
2117    $username = trim($username);
2118    // Consolidate contiguous whitespace
2119    $username = preg_replace('|\s+|', ' ', $username);
2120
2121    /**
2122     * Filters a sanitized username string.
2123     *
2124     * @since 2.0.1
2125     *
2126     * @param string $username     Sanitized username.
2127     * @param string $raw_username The username prior to sanitization.
2128     * @param bool   $strict       Whether to limit the sanitization to specific characters. Default false.
2129     */
2130    return apply_filters('sanitize_user', $username, $raw_username, $strict);
2131}
2132
2133/**
2134 * Sanitizes a string key.
2135 *
2136 * Keys are used as internal identifiers. Lowercase alphanumeric characters, dashes and underscores are allowed.
2137 *
2138 * @since 3.0.0
2139 *
2140 * @param string $key String key
2141 * @return string Sanitized key
2142 */
2143function sanitize_key($key)
2144{
2145    $raw_key = $key;
2146    $key     = strtolower($key);
2147    $key     = preg_replace('/[^a-z0-9_\-]/', '', $key);
2148
2149    /**
2150     * Filters a sanitized key string.
2151     *
2152     * @since 3.0.0
2153     *
2154     * @param string $key     Sanitized key.
2155     * @param string $raw_key The key prior to sanitization.
2156     */
2157    return apply_filters('sanitize_key', $key, $raw_key);
2158}
2159
2160/**
2161 * Sanitizes a title, or returns a fallback title.
2162 *
2163 * Specifically, HTML and PHP tags are stripped. Further actions can be added
2164 * via the plugin API. If $title is empty and $fallback_title is set, the latter
2165 * will be used.
2166 *
2167 * @since 1.0.0
2168 *
2169 * @param string $title          The string to be sanitized.
2170 * @param string $fallback_title Optional. A title to use if $title is empty.
2171 * @param string $context        Optional. The operation for which the string is sanitized
2172 * @return string The sanitized string.
2173 */
2174function sanitize_title($title, $fallback_title = '', $context = 'save')
2175{
2176    $raw_title = $title;
2177
2178    if ('save' == $context) {
2179        $title = remove_accents($title);
2180    }
2181
2182    /**
2183     * Filters a sanitized title string.
2184     *
2185     * @since 1.2.0
2186     *
2187     * @param string $title     Sanitized title.
2188     * @param string $raw_title The title prior to sanitization.
2189     * @param string $context   The context for which the title is being sanitized.
2190     */
2191    $title = apply_filters('sanitize_title', $title, $raw_title, $context);
2192
2193    if ('' === $title || false === $title) {
2194        $title = $fallback_title;
2195    }
2196
2197    return $title;
2198}
2199
2200/**
2201 * Sanitizes a title with the 'query' context.
2202 *
2203 * Used for querying the database for a value from URL.
2204 *
2205 * @since 3.1.0
2206 *
2207 * @param string $title The string to be sanitized.
2208 * @return string The sanitized string.
2209 */
2210function sanitize_title_for_query($title)
2211{
2212    return sanitize_title($title, '', 'query');
2213}
2214
2215/**
2216 * Sanitizes a title, replacing whitespace and a few other characters with dashes.
2217 *
2218 * Limits the output to alphanumeric characters, underscore (_) and dash (-).
2219 * Whitespace becomes a dash.
2220 *
2221 * @since 1.2.0
2222 *
2223 * @param string $title     The title to be sanitized.
2224 * @param string $raw_title Optional. Not used.
2225 * @param string $context   Optional. The operation for which the string is sanitized.
2226 * @return string The sanitized title.
2227 */
2228function sanitize_title_with_dashes($title, $raw_title = '', $context = 'display')
2229{
2230    $title = strip_tags($title);
2231    // Preserve escaped octets.
2232    $title = preg_replace('|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title);
2233    // Remove percent signs that are not part of an octet.
2234    $title = str_replace('%', '', $title);
2235    // Restore octets.
2236    $title = preg_replace('|---([a-fA-F0-9][a-fA-F0-9])---|', '%$1', $title);
2237
2238    if (seems_utf8($title)) {
2239        if (function_exists('mb_strtolower')) {
2240            $title = mb_strtolower($title, 'UTF-8');
2241        }
2242        $title = utf8_uri_encode($title, 200);
2243    }
2244
2245    $title = strtolower($title);
2246
2247    if ('save' == $context) {
2248        // Convert nbsp, ndash and mdash to hyphens
2249        $title = str_replace(array( '%c2%a0', '%e2%80%93', '%e2%80%94' ), '-', $title);
2250        // Convert nbsp, ndash and mdash HTML entities to hyphens
2251        $title = str_replace(array( '&nbsp;', '&#160;', '&ndash;', '&#8211;', '&mdash;', '&#8212;' ), '-', $title);
2252        // Convert forward slash to hyphen
2253        $title = str_replace('/', '-', $title);
2254
2255        // Strip these characters entirely
2256        $title = str_replace(
2257            array(
2258                // iexcl and iquest
2259                '%c2%a1',
2260                '%c2%bf',
2261                // angle quotes
2262                '%c2%ab',
2263                '%c2%bb',
2264                '%e2%80%b9',
2265                '%e2%80%ba',
2266                // curly quotes
2267                '%e2%80%98',
2268                '%e2%80%99',
2269                '%e2%80%9c',
2270                '%e2%80%9d',
2271                '%e2%80%9a',
2272                '%e2%80%9b',
2273                '%e2%80%9e',
2274                '%e2%80%9f',
2275                // copy, reg, deg, hellip and trade
2276                '%c2%a9',
2277                '%c2%ae',
2278                '%c2%b0',
2279                '%e2%80%a6',
2280                '%e2%84%a2',
2281                // acute accents
2282                '%c2%b4',
2283                '%cb%8a',
2284                '%cc%81',
2285                '%cd%81',
2286                // grave accent, macron, caron
2287                '%cc%80',
2288                '%cc%84',
2289                '%cc%8c',
2290            ),
2291            '',
2292            $title
2293        );
2294
2295        // Convert times to x
2296        $title = str_replace('%c3%97', 'x', $title);
2297    }
2298
2299    $title = preg_replace('/&.+?;/', '', $title); // kill entities
2300    $title = str_replace('.', '-', $title);
2301
2302    $title = preg_replace('/[^%a-z0-9 _-]/', '', $title);
2303    $title = preg_replace('/\s+/', '-', $title);
2304    $title = preg_replace('|-+|', '-', $title);
2305    $title = trim($title, '-');
2306
2307    return $title;
2308}
2309
2310/**
2311 * Ensures a string is a valid SQL 'order by' clause.
2312 *
2313 * Accepts one or more columns, with or without a sort order (ASC / DESC).
2314 * e.g. 'column_1', 'column_1, column_2', 'column_1 ASC, column_2 DESC' etc.
2315 *
2316 * Also accepts 'RAND()'.
2317 *
2318 * @since 2.5.1
2319 *
2320 * @param string $orderby Order by clause to be validated.
2321 * @return string|false Returns $orderby if valid, false otherwise.
2322 */
2323function sanitize_sql_orderby($orderby)
2324{
2325    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)) {
2326        return $orderby;
2327    }
2328    return false;
2329}
2330
2331/**
2332 * Sanitizes an HTML classname to ensure it only contains valid characters.
2333 *
2334 * Strips the string down to A-Z,a-z,0-9,_,-. If this results in an empty
2335 * string then it will return the alternative value supplied.
2336 *
2337 * @todo Expand to support the full range of CDATA that a class attribute can contain.
2338 *
2339 * @since 2.8.0
2340 *
2341 * @param string $class    The classname to be sanitized
2342 * @param string $fallback Optional. The value to return if the sanitization ends up as an empty string.
2343 *  Defaults to an empty string.
2344 * @return string The sanitized value
2345 */
2346function sanitize_html_class($class, $fallback = '')
2347{
2348    //Strip out any % encoded octets
2349    $sanitized = preg_replace('|%[a-fA-F0-9][a-fA-F0-9]|', '', $class);
2350
2351    //Limit to A-Z,a-z,0-9,_,-
2352    $sanitized = preg_replace('/[^A-Za-z0-9_-]/', '', $sanitized);
2353
2354    if ('' == $sanitized && $fallback) {
2355        return sanitize_html_class($fallback);
2356    }
2357    /**
2358     * Filters a sanitized HTML class string.
2359     *
2360     * @since 2.8.0
2361     *
2362     * @param string $sanitized The sanitized HTML class.
2363     * @param string $class     HTML class before sanitization.
2364     * @param string $fallback  The fallback string.
2365     */
2366    return apply_filters('sanitize_html_class', $sanitized, $class, $fallback);
2367}
2368
2369/**
2370 * Converts lone & characters into `&#038;` (a.k.a. `&amp;`)
2371 *
2372 * @since 0.71
2373 *
2374 * @param string $content    String of characters to be converted.
2375 * @param string $deprecated Not used.
2376 * @return string Converted string.
2377 */
2378function convert_chars($content, $deprecated = '')
2379{
2380    if (! empty($deprecated)) {
2381        _deprecated_argument(__FUNCTION__, '0.71');
2382    }
2383
2384    if (strpos($content, '&') !== false) {
2385        $content = preg_replace('/&([^#])(?![a-z1-4]{1,8};)/i', '&#038;$1', $content);
2386    }
2387
2388    return $content;
2389}
2390
2391/**
2392 * Converts invalid Unicode references range to valid range.
2393 *
2394 * @since 4.3.0
2395 *
2396 * @param string $content String with entities that need converting.
2397 * @return string Converted string.
2398 */
2399function convert_invalid_entities($content)
2400{
2401    $wp_htmltranswinuni = array(
2402        '&#128;' => '&#8364;', // the Euro sign
2403        '&#129;' => '',
2404        '&#130;' => '&#8218;', // these are Windows CP1252 specific characters
2405        '&#131;' => '&#402;',  // they would look weird on non-Windows browsers
2406        '&#132;' => '&#8222;',
2407        '&#133;' => '&#8230;',
2408        '&#134;' => '&#8224;',
2409        '&#135;' => '&#8225;',
2410        '&#136;' => '&#710;',
2411        '&#137;' => '&#8240;',
2412        '&#138;' => '&#352;',
2413        '&#139;' => '&#8249;',
2414        '&#140;' => '&#338;',
2415        '&#141;' => '',
2416        '&#142;' => '&#381;',
2417        '&#143;' => '',
2418        '&#144;' => '',
2419        '&#145;' => '&#8216;',
2420        '&#146;' => '&#8217;',
2421        '&#147;' => '&#8220;',
2422        '&#148;' => '&#8221;',
2423        '&#149;' => '&#8226;',
2424        '&#150;' => '&#8211;',
2425        '&#151;' => '&#8212;',
2426        '&#152;' => '&#732;',
2427        '&#153;' => '&#8482;',
2428        '&#154;' => '&#353;',
2429        '&#155;' => '&#8250;',
2430        '&#156;' => '&#339;',
2431        '&#157;' => '',
2432        '&#158;' => '&#382;',
2433        '&#159;' => '&#376;',
2434    );
2435
2436    if (strpos($content, '&#1') !== false) {
2437        $content = strtr($content, $wp_htmltranswinuni);
2438    }
2439
2440    return $content;
2441}
2442
2443/**
2444 * Balances tags if forced to, or if the 'use_balanceTags' option is set to true.
2445 *
2446 * @since 0.71
2447 *
2448 * @param string $text  Text to be balanced
2449 * @param bool   $force If true, forces balancing, ignoring the value of the option. Default false.
2450 * @return string Balanced text
2451 */
2452function balanceTags($text, $force = false)
2453{
2454    if ($force || get_option('use_balanceTags') == 1) {
2455        return force_balance_tags($text);
2456    } else {
2457        return $text;
2458    }
2459}
2460
2461/**
2462 * Balances tags of string using a modified stack.
2463 *
2464 * @since 2.0.4
2465 *
2466 * @author Leonard Lin <leonard@acm.org>
2467 * @license GPL
2468 * @copyright November 4, 2001
2469 * @version 1.1
2470 * @todo Make better - change loop condition to $text in 1.2
2471 * @internal Modified by Scott Reilly (coffee2code) 02 Aug 2004
2472 *      1.1  Fixed handling of append/stack pop order of end text
2473 *           Added Cleaning Hooks
2474 *      1.0  First Version
2475 *
2476 * @param string $text Text to be balanced.
2477 * @return string Balanced text.
2478 */
2479function force_balance_tags($text)
2480{
2481    $tagstack  = array();
2482    $stacksize = 0;
2483    $tagqueue  = '';
2484    $newtext   = '';
2485    // Known single-entity/self-closing tags
2486    $single_tags = array( 'area', 'base', 'basefont', 'br', 'col', 'command', 'embed', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param', 'source' );
2487    // Tags that can be immediately nested within themselves
2488    $nestable_tags = array( 'blockquote', 'div', 'object', 'q', 'span' );
2489
2490    // WP bug fix for comments - in case you REALLY meant to type '< !--'
2491    $text = str_replace('< !--', '<    !--', $text);
2492    // WP bug fix for LOVE <3 (and other situations with '<' before a number)
2493    $text = preg_replace('#<([0-9]{1})#', '&lt;$1', $text);
2494
2495    while (preg_match('/<(\/?[\w:]*)\s*([^>]*)>/', $text, $regex)) {
2496        $newtext .= $tagqueue;
2497
2498        $i = strpos($text, $regex[0]);
2499        $l = strlen($regex[0]);
2500
2501        // clear the shifter
2502        $tagqueue = '';
2503        // Pop or Push
2504        if (isset($regex[1][0]) && '/' == $regex[1][0]) { // End Tag
2505            $tag = strtolower(substr($regex[1], 1));
2506            // if too many closing tags
2507            if ($stacksize <= 0) {
2508                $tag = '';
2509                // or close to be safe $tag = '/' . $tag;
2510            } // if stacktop value = tag close value then pop
2511            elseif ($tagstack[ $stacksize - 1 ] == $tag) { // found closing tag
2512                $tag = '</' . $tag . '>'; // Close Tag
2513                // Pop
2514                array_pop($tagstack);
2515                $stacksize--;
2516            } else { // closing tag not at top, search for it
2517                for ($j = $stacksize - 1; $j >= 0; $j--) {
2518                    if ($tagstack[ $j ] == $tag) {
2519                        // add tag to tagqueue
2520                        for ($k = $stacksize - 1; $k >= $j; $k--) {
2521                            $tagqueue .= '</' . array_pop($tagstack) . '>';
2522                            $stacksize--;
2523                        }
2524                        break;
2525                    }
2526                }
2527                $tag = '';
2528            }
2529        } else { // Begin Tag
2530            $tag = strtolower($regex[1]);
2531
2532            // Tag Cleaning
2533
2534            // If it's an empty tag "< >", do nothing
2535            if ('' == $tag) {
2536                // do nothing
2537            } // ElseIf it presents itself as a self-closing tag...
2538            elseif (substr($regex[2], -1) == '/') {
2539                // ...but it isn't a known single-entity self-closing tag, then don't let it be treated as such and
2540                // immediately close it with a closing tag (the tag will encapsulate no text as a result)
2541                if (! in_array($tag, $single_tags)) {
2542                    $regex[2] = trim(substr($regex[2], 0, -1)) . "></$tag";
2543                }
2544            } // ElseIf it's a known single-entity tag but it doesn't close itself, do so
2545            elseif (in_array($tag, $single_tags)) {
2546                $regex[2] .= '/';
2547            } // Else it's not a single-entity tag
2548            else {
2549                // If the top of the stack is the same as the tag we want to push, close previous tag
2550                if ($stacksize > 0 && ! in_array($tag, $nestable_tags) && $tagstack[ $stacksize - 1 ] == $tag) {
2551                    $tagqueue = '</' . array_pop($tagstack) . '>';
2552                    $stacksize--;
2553                }
2554                $stacksize = array_push($tagstack, $tag);
2555            }
2556
2557            // Attributes
2558            $attributes = $regex[2];
2559            if (! empty($attributes) && $attributes[0] != '>') {
2560                $attributes = ' ' . $attributes;
2561            }
2562
2563            $tag = '<' . $tag . $attributes . '>';
2564            //If already queuing a close tag, then put this tag on, too
2565            if (! empty($tagqueue)) {
2566                $tagqueue .= $tag;
2567                $tag       = '';
2568            }
2569        }
2570        $newtext .= substr($text, 0, $i) . $tag;
2571        $text     = substr($text, $i + $l);
2572    }
2573
2574    // Clear Tag Queue
2575    $newtext .= $tagqueue;
2576
2577    // Add Remaining text
2578    $newtext .= $text;
2579
2580    // Empty Stack
2581    while ($x = array_pop($tagstack)) {
2582        $newtext .= '</' . $x . '>'; // Add remaining tags to close
2583    }
2584
2585    // WP fix for the bug with HTML comments
2586    $newtext = str_replace('< !--', '<!--', $newtext);
2587    $newtext = str_replace('<    !--', '< !--', $newtext);
2588
2589    return $newtext;
2590}
2591
2592/**
2593 * Acts on text which is about to be edited.
2594 *
2595 * The $content is run through esc_textarea(), which uses htmlspecialchars()
2596 * to convert special characters to HTML entities. If `$richedit` is set to true,
2597 * it is simply a holder for the {@see 'format_to_edit'} filter.
2598 *
2599 * @since 0.71
2600 * @since 4.4.0 The `$richedit` parameter was renamed to `$rich_text` for clarity.
2601 *
2602 * @param string $content   The text about to be edited.
2603 * @param bool   $rich_text Optional. Whether `$content` should be considered rich text,
2604 *                          in which case it would not be passed through esc_textarea().
2605 *                          Default false.
2606 * @return string The text after the filter (and possibly htmlspecialchars()) has been run.
2607 */
2608function format_to_edit($content, $rich_text = false)
2609{
2610    /**
2611     * Filters the text to be formatted for editing.
2612     *
2613     * @since 1.2.0
2614     *
2615     * @param string $content The text, prior to formatting for editing.
2616     */
2617    $content = apply_filters('format_to_edit', $content);
2618    if (! $rich_text) {
2619        $content = esc_textarea($content);
2620    }
2621    return $content;
2622}
2623
2624/**
2625 * Add leading zeros when necessary.
2626 *
2627 * If you set the threshold to '4' and the number is '10', then you will get
2628 * back '0010'. If you set the threshold to '4' and the number is '5000', then you
2629 * will get back '5000'.
2630 *
2631 * Uses sprintf to append the amount of zeros based on the $threshold parameter
2632 * and the size of the number. If the number is large enough, then no zeros will
2633 * be appended.
2634 *
2635 * @since 0.71
2636 *
2637 * @param int $number     Number to append zeros to if not greater than threshold.
2638 * @param int $threshold  Digit places number needs to be to not have zeros added.
2639 * @return string Adds leading zeros to number if needed.
2640 */
2641function zeroise($number, $threshold)
2642{
2643    return sprintf('%0' . $threshold . 's', $number);
2644}
2645
2646/**
2647 * Adds backslashes before letters and before a number at the start of a string.
2648 *
2649 * @since 0.71
2650 *
2651 * @param string $string Value to which backslashes will be added.
2652 * @return string String with backslashes inserted.
2653 */
2654function backslashit($string)
2655{
2656    if (isset($string[0]) && $string[0] >= '0' && $string[0] <= '9') {
2657        $string = '\\\\' . $string;
2658    }
2659    return addcslashes($string, 'A..Za..z');
2660}
2661
2662/**
2663 * Appends a trailing slash.
2664 *
2665 * Will remove trailing forward and backslashes if it exists already before adding
2666 * a trailing forward slash. This prevents double slashing a string or path.
2667 *
2668 * The primary use of this is for paths and thus should be used for paths. It is
2669 * not restricted to paths and offers no specific path support.
2670 *
2671 * @since 1.2.0
2672 *
2673 * @param string $string What to add the trailing slash to.
2674 * @return string String with trailing slash added.
2675 */
2676function trailingslashit($string)
2677{
2678    return untrailingslashit($string) . '/';
2679}
2680
2681/**
2682 * Removes trailing forward slashes and backslashes if they exist.
2683 *
2684 * The primary use of this is for paths and thus should be used for paths. It is
2685 * not restricted to paths and offers no specific path support.
2686 *
2687 * @since 2.2.0
2688 *
2689 * @param string $string What to remove the trailing slashes from.
2690 * @return string String without the trailing slashes.
2691 */
2692function untrailingslashit($string)
2693{
2694    return rtrim($string, '/\\');
2695}
2696
2697/**
2698 * Adds slashes to escape strings.
2699 *
2700 * Slashes will first be removed if magic_quotes_gpc is set, see {@link
2701 * https://secure.php.net/magic_quotes} for more details.
2702 *
2703 * @since 0.71
2704 *
2705 * @param string $gpc The string returned from HTTP request data.
2706 * @return string Returns a string escaped with slashes.
2707 */
2708function addslashes_gpc($gpc)
2709{
2710    if (get_magic_quotes_gpc()) {
2711        $gpc = stripslashes($gpc);
2712    }
2713
2714    return wp_slash($gpc);
2715}
2716
2717/**
2718 * Navigates through an array, object, or scalar, and removes slashes from the values.
2719 *
2720 * @since 2.0.0
2721 *
2722 * @param mixed $value The value to be stripped.
2723 * @return mixed Stripped value.
2724 */
2725function stripslashes_deep($value)
2726{
2727    return map_deep($value, 'stripslashes_from_strings_only');
2728}
2729
2730/**
2731 * Callback function for `stripslashes_deep()` which strips slashes from strings.
2732 *
2733 * @since 4.4.0
2734 *
2735 * @param mixed $value The array or string to be stripped.
2736 * @return mixed $value The stripped value.
2737 */
2738function stripslashes_from_strings_only($value)
2739{
2740    return is_string($value) ? stripslashes($value) : $value;
2741}
2742
2743/**
2744 * Navigates through an array, object, or scalar, and encodes the values to be used in a URL.
2745 *
2746 * @since 2.2.0
2747 *
2748 * @param mixed $value The array or string to be encoded.
2749 * @return mixed $value The encoded value.
2750 */
2751function urlencode_deep($value)
2752{
2753    return map_deep($value, 'urlencode');
2754}
2755
2756/**
2757 * Navigates through an array, object, or scalar, and raw-encodes the values to be used in a URL.
2758 *
2759 * @since 3.4.0
2760 *
2761 * @param mixed $value The array or string to be encoded.
2762 * @return mixed $value The encoded value.
2763 */
2764function rawurlencode_deep($value)
2765{
2766    return map_deep($value, 'rawurlencode');
2767}
2768
2769/**
2770 * Navigates through an array, object, or scalar, and decodes URL-encoded values
2771 *
2772 * @since 4.4.0
2773 *
2774 * @param mixed $value The array or string to be decoded.
2775 * @return mixed $value The decoded value.
2776 */
2777function urldecode_deep($value)
2778{
2779    return map_deep($value, 'urldecode');
2780}
2781
2782/**
2783 * Converts email addresses characters to HTML entities to block spam bots.
2784 *
2785 * @since 0.71
2786 *
2787 * @param string $email_address Email address.
2788 * @param int    $hex_encoding  Optional. Set to 1 to enable hex encoding.
2789 * @return string Converted email address.
2790 */
2791function antispambot($email_address, $hex_encoding = 0)
2792{
2793    $email_no_spam_address = '';
2794    for ($i = 0, $len = strlen($email_address); $i < $len; $i++) {
2795        $j = rand(0, 1 + $hex_encoding);
2796        if ($j == 0) {
2797            $email_no_spam_address .= '&#' . ord($email_address[ $i ]) . ';';
2798        } elseif ($j == 1) {
2799            $email_no_spam_address .= $email_address[ $i ];
2800        } elseif ($j == 2) {
2801            $email_no_spam_address .= '%' . zeroise(dechex(ord($email_address[ $i ])), 2);
2802        }
2803    }
2804
2805    return str_replace('@', '&#64;', $email_no_spam_address);
2806}
2807
2808/**
2809 * Callback to convert URI match to HTML A element.
2810 *
2811 * This function was backported from 2.5.0 to 2.3.2. Regex callback for make_clickable().
2812 *
2813 * @since 2.3.2
2814 * @access private
2815 *
2816 * @param array $matches Single Regex Match.
2817 * @return string HTML A element with URI address.
2818 */
2819function _make_url_clickable_cb($matches)
2820{
2821    $url = $matches[2];
2822
2823    if (')' == $matches[3] && strpos($url, '(')) {
2824        // If the trailing character is a closing parethesis, and the URL has an opening parenthesis in it, add the closing parenthesis to the URL.
2825        // Then we can let the parenthesis balancer do its thing below.
2826        $url   .= $matches[3];
2827        $suffix = '';
2828    } else {
2829        $suffix = $matches[3];
2830    }
2831
2832    // Include parentheses in the URL only if paired
2833    while (substr_count($url, '(') < substr_count($url, ')')) {
2834        $suffix = strrchr($url, ')') . $suffix;
2835        $url    = substr($url, 0, strrpos($url, ')'));
2836    }
2837
2838    $url = esc_url($url);
2839    if (empty($url)) {
2840        return $matches[0];
2841    }
2842
2843    return $matches[1] . "<a href=\"$url\" rel=\"nofollow\">$url</a>" . $suffix;
2844}
2845
2846/**
2847 * Callback to convert URL match to HTML A element.
2848 *
2849 * This function was backported from 2.5.0 to 2.3.2. Regex callback for make_clickable().
2850 *
2851 * @since 2.3.2
2852 * @access private
2853 *
2854 * @param array $matches Single Regex Match.
2855 * @return string HTML A element with URL address.
2856 */
2857function _make_web_ftp_clickable_cb($matches)
2858{
2859    $ret  = '';
2860    $dest = $matches[2];
2861    $dest = 'http://' . $dest;
2862
2863    // removed trailing [.,;:)] from URL
2864    if (in_array(substr($dest, -1), array( '.', ',', ';', ':', ')' )) === true) {
2865        $ret  = substr($dest, -1);
2866        $dest = substr($dest, 0, strlen($dest) - 1);
2867    }
2868
2869    $dest = esc_url($dest);
2870    if (empty($dest)) {
2871        return $matches[0];
2872    }
2873
2874    return $matches[1] . "<a href=\"$dest\" rel=\"nofollow\">$dest</a>$ret";
2875}
2876
2877/**
2878 * Callback to convert email address match to HTML A element.
2879 *
2880 * This function was backported from 2.5.0 to 2.3.2. Regex callback for make_clickable().
2881 *
2882 * @since 2.3.2
2883 * @access private
2884 *
2885 * @param array $matches Single Regex Match.
2886 * @return string HTML A element with email address.
2887 */
2888function _make_email_clickable_cb($matches)
2889{
2890    $email = $matches[2] . '@' . $matches[3];
2891    return $matches[1] . "<a href=\"mailto:$email\">$email</a>";
2892}
2893
2894/**
2895 * Convert plaintext URI to HTML links.
2896 *
2897 * Converts URI, www and ftp, and email addresses. Finishes by fixing links
2898 * within links.
2899 *
2900 * @since 0.71
2901 *
2902 * @param string $text Content to convert URIs.
2903 * @return string Content with converted URIs.
2904 */
2905function make_clickable($text)
2906{
2907    $r               = '';
2908    $textarr         = preg_split('/(<[^<>]+>)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE); // split out HTML tags
2909    $nested_code_pre = 0; // Keep track of how many levels link is nested inside <pre> or <code>
2910    foreach ($textarr as $piece) {
2911        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)) {
2912            $nested_code_pre++;
2913        } elseif ($nested_code_pre && ( '</code>' === strtolower($piece) || '</pre>' === strtolower($piece) || '</script>' === strtolower($piece) || '</style>' === strtolower($piece) )) {
2914            $nested_code_pre--;
2915        }
2916
2917        if ($nested_code_pre || empty($piece) || ( $piece[0] === '<' && ! preg_match('|^<\s*[\w]{1,20}+://|', $piece) )) {
2918            $r .= $piece;
2919            continue;
2920        }
2921
2922        // Long strings might contain expensive edge cases ...
2923        if (10000 < strlen($piece)) {
2924            // ... break it up
2925            foreach (_split_str_by_whitespace($piece, 2100) as $chunk) { // 2100: Extra room for scheme and leading and trailing paretheses
2926                if (2101 < strlen($chunk)) {
2927                    $r .= $chunk; // Too big, no whitespace: bail.
2928                } else {
2929                    $r .= make_clickable($chunk);
2930                }
2931            }
2932        } else {
2933            $ret = " $piece "; // Pad with whitespace to simplify the regexes
2934
2935            $url_clickable = '~
2936                                ([\\s(<.,;:!?])                                        # 1: Leading whitespace, or punctuation
2937                                (                                                      # 2: URL
2938                                        [\\w]{1,20}+://                                # Scheme and hier-part prefix
2939                                        (?=\S{1,2000}\s)                               # Limit to URLs less than about 2000 characters long
2940                                        [\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]*+         # Non-punctuation URL character
2941                                        (?:                                            # Unroll the Loop: Only allow puctuation URL character if followed by a non-punctuation URL character
2942                                                [\'.,;:!?)]                            # Punctuation URL character
2943                                                [\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]++ # Non-punctuation URL character
2944                                        )*
2945                                )
2946                                (\)?)                                                  # 3: Trailing closing parenthesis (for parethesis balancing post processing)
2947                        ~xS'; // The regex is a non-anchored pattern and does not have a single fixed starting character.
2948                  // Tell PCRE to spend more time optimizing since, when used on a page load, it will probably be used several times.
2949
2950            $ret = preg_replace_callback($url_clickable, '_make_url_clickable_cb', $ret);
2951
2952            $ret = preg_replace_callback('#([\s>])((www|ftp)\.[\w\\x80-\\xff\#$%&~/.\-;:=,?@\[\]+]+)#is', '_make_web_ftp_clickable_cb', $ret);
2953            $ret = preg_replace_callback('#([\s>])([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})#i', '_make_email_clickable_cb', $ret);
2954
2955            $ret = substr($ret, 1, -1); // Remove our whitespace padding.
2956            $r  .= $ret;
2957        }
2958    }
2959
2960    // Cleanup of accidental links within links
2961    return preg_replace('#(<a([ \r\n\t]+[^>]+?>|>))<a [^>]+?>([^>]+?)</a></a>#i', '$1$3</a>', $r);
2962}
2963
2964/**
2965 * Breaks a string into chunks by splitting at whitespace characters.
2966 * The length of each returned chunk is as close to the specified length goal as possible,
2967 * with the caveat that each chunk includes its trailing delimiter.
2968 * Chunks longer than the goal are guaranteed to not have any inner whitespace.
2969 *
2970 * Joining the returned chunks with empty delimiters reconstructs the input string losslessly.
2971 *
2972 * Input string must have no null characters (or eventual transformations on output chunks must not care about null characters)
2973 *
2974 *     _split_str_by_whitespace( "1234 67890 1234 67890a cd 1234   890 123456789 1234567890a    45678   1 3 5 7 90 ", 10 ) ==
2975 *     array (
2976 *         0 => '1234 67890 ',  // 11 characters: Perfect split
2977 *         1 => '1234 ',        //  5 characters: '1234 67890a' was too long
2978 *         2 => '67890a cd ',   // 10 characters: '67890a cd 1234' was too long
2979 *         3 => '1234   890 ',  // 11 characters: Perfect split
2980 *         4 => '123456789 ',   // 10 characters: '123456789 1234567890a' was too long
2981 *         5 => '1234567890a ', // 12 characters: Too long, but no inner whitespace on which to split
2982 *         6 => '   45678   ',  // 11 characters: Perfect split
2983 *         7 => '1 3 5 7 90 ',  // 11 characters: End of $string
2984 *     );
2985 *
2986 * @since 3.4.0
2987 * @access private
2988 *
2989 * @param string $string The string to split.
2990 * @param int    $goal   The desired chunk length.
2991 * @return array Numeric array of chunks.
2992 */
2993function _split_str_by_whitespace($string, $goal)
2994{
2995    $chunks = array();
2996
2997    $string_nullspace = strtr($string, "\r\n\t\v\f ", "\000\000\000\000\000\000");
2998
2999    while ($goal < strlen($string_nullspace)) {
3000        $pos = strrpos(substr($string_nullspace, 0, $goal + 1), "\000");
3001
3002        if (false === $pos) {
3003            $pos = strpos($string_nullspace, "\000", $goal + 1);
3004            if (false === $pos) {
3005                break;
3006            }
3007        }
3008
3009        $chunks[]         = substr($string, 0, $pos + 1);
3010        $string           = substr($string, $pos + 1);
3011        $string_nullspace = substr($string_nullspace, $pos + 1);
3012    }
3013
3014    if ($string) {
3015        $chunks[] = $string;
3016    }
3017
3018    return $chunks;
3019}
3020
3021/**
3022 * Adds rel nofollow string to all HTML A elements in content.
3023 *
3024 * @since 1.5.0
3025 *
3026 * @param string $text Content that may contain HTML A elements.
3027 * @return string Converted content.
3028 */
3029function wp_rel_nofollow($text)
3030{
3031    // This is a pre save filter, so text is already escaped.
3032    $text = stripslashes($text);
3033    $text = preg_replace_callback('|<a (.+?)>|i', 'wp_rel_nofollow_callback', $text);
3034    return wp_slash($text);
3035}
3036
3037/**
3038 * Callback to add rel=nofollow string to HTML A element.
3039 *
3040 * Will remove already existing rel="nofollow" and rel='nofollow' from the
3041 * string to prevent from invalidating (X)HTML.
3042 *
3043 * @since 2.3.0
3044 *
3045 * @param array $matches Single Match
3046 * @return string HTML A Element with rel nofollow.
3047 */
3048function wp_rel_nofollow_callback($matches)
3049{
3050    $text = $matches[1];
3051    $atts = shortcode_parse_atts($matches[1]);
3052    $rel  = 'nofollow';
3053
3054    if (preg_match('%href=["\'](' . preg_quote(set_url_scheme(home_url(), 'http')) . ')%i', $text) ||
3055        preg_match('%href=["\'](' . preg_quote(set_url_scheme(home_url(), 'https')) . ')%i', $text) ) {
3056        return "<a $text>";
3057    }
3058
3059    if (! empty($atts['rel'])) {
3060        $parts = array_map('trim', explode(' ', $atts['rel']));
3061        if (false === array_search('nofollow', $parts)) {
3062            $parts[] = 'nofollow';
3063        }
3064        $rel = implode(' ', $parts);
3065        unset($atts['rel']);
3066
3067        $html = '';
3068        foreach ($atts as $name => $value) {
3069            $html .= "{$name}=\"$value\" ";
3070        }
3071        $text = trim($html);
3072    }
3073    return "<a $text rel=\"$rel\">";
3074}
3075
3076/**
3077 * Adds rel noreferrer and noopener to all HTML A elements that have a target.
3078 *
3079 * @param string $text Content that may contain HTML A elements.
3080 * @return string Converted content.
3081 */
3082function wp_targeted_link_rel($text)
3083{
3084    // Don't run (more expensive) regex if no links with targets.
3085    if (stripos($text, 'target') !== false && stripos($text, '<a ') !== false) {
3086        $text = preg_replace_callback('|<a\s([^>]*target\s*=[^>]*)>|i', 'wp_targeted_link_rel_callback', $text);
3087    }
3088
3089    return $text;
3090}
3091
3092/**
3093 * Callback to add rel="noreferrer noopener" string to HTML A element.
3094 *
3095 * Will not duplicate existing noreferrer and noopener values
3096 * to prevent from invalidating the HTML.
3097 *
3098 * @param array $matches Single Match
3099 * @return string HTML A Element with rel noreferrer noopener in addition to any existing values
3100 */
3101function wp_targeted_link_rel_callback($matches)
3102{
3103    $link_html = $matches[1];
3104    $rel_match = array();
3105
3106    /**
3107     * Filters the rel values that are added to links with `target` attribute.
3108     *
3109     * @since 5.0.0
3110     *
3111     * @param string The rel values.
3112     * @param string $link_html The matched content of the link tag including all HTML attributes.
3113     */
3114    $rel = apply_filters('wp_targeted_link_rel', 'noopener noreferrer', $link_html);
3115
3116    // Value with delimiters, spaces around are optional.
3117    $attr_regex = '|rel\s*=\s*?(\\\\{0,1}["\'])(.*?)\\1|i';
3118    preg_match($attr_regex, $link_html, $rel_match);
3119
3120    if (empty($rel_match[0])) {
3121        // No delimiters, try with a single value and spaces, because `rel =  va"lue` is totally fine...
3122        $attr_regex = '|rel\s*=(\s*)([^\s]*)|i';
3123        preg_match($attr_regex, $link_html, $rel_match);
3124    }
3125
3126    if (! empty($rel_match[0])) {
3127        $parts = preg_split('|\s+|', strtolower($rel_match[2]));
3128        $parts = array_map('esc_attr', $parts);
3129        $needed = explode(' ', $rel);
3130        $parts = array_unique(array_merge($parts, $needed));
3131        $delimiter = trim($rel_match[1]) ? $rel_match[1] : '"';
3132        $rel = 'rel=' . $delimiter . trim(implode(' ', $parts)) . $delimiter;
3133        $link_html = str_replace($rel_match[0], $rel, $link_html);
3134    } else {
3135        $link_html .= " rel=\"$rel\"";
3136    }
3137
3138    return "<a $link_html>";
3139}
3140
3141/**
3142 * Convert one smiley code to the icon graphic file equivalent.
3143 *
3144 * Callback handler for convert_smilies().
3145 *
3146 * Looks up one smiley code in the $wpsmiliestrans global array and returns an
3147 * `<img>` string for that smiley.
3148 *
3149 * @since 2.8.0
3150 *
3151 * @global array $wpsmiliestrans
3152 *
3153 * @param array $matches Single match. Smiley code to convert to image.
3154 * @return string Image string for smiley.
3155 */
3156function translate_smiley($matches)
3157{
3158    global $wpsmiliestrans;
3159
3160    if (count($matches) == 0) {
3161        return '';
3162    }
3163
3164    $smiley = trim(reset($matches));
3165    $img    = $wpsmiliestrans[ $smiley ];
3166
3167    $matches    = array();
3168    $ext        = preg_match('/\.([^.]+)$/', $img, $matches) ? strtolower($matches[1]) : false;
3169    $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png' );
3170
3171    // Don't convert smilies that aren't images - they're probably emoji.
3172    if (! in_array($ext, $image_exts)) {
3173        return $img;
3174    }
3175
3176    /**
3177     * Filters the Smiley image URL before it's used in the image element.
3178     *
3179     * @since 2.9.0
3180     *
3181     * @param string $smiley_url URL for the smiley image.
3182     * @param string $img        Filename for the smiley image.
3183     * @param string $site_url   Site URL, as returned by site_url().
3184     */
3185    $src_url = apply_filters('smilies_src', includes_url("images/smilies/$img"), $img, site_url());
3186
3187    return sprintf('<img src="%s" alt="%s" class="wp-smiley" style="height: 1em; max-height: 1em;" />', esc_url($src_url), esc_attr($smiley));
3188}
3189
3190/**
3191 * Convert text equivalent of smilies to images.
3192 *
3193 * Will only convert smilies if the option 'use_smilies' is true and the global
3194 * used in the function isn't empty.
3195 *
3196 * @since 0.71
3197 *
3198 * @global string|array $wp_smiliessearch
3199 *
3200 * @param string $text Content to convert smilies from text.
3201 * @return string Converted content with text smilies replaced with images.
3202 */
3203function convert_smilies($text)
3204{
3205    global $wp_smiliessearch;
3206    $output = '';
3207    if (get_option('use_smilies') && ! empty($wp_smiliessearch)) {
3208        // HTML loop taken from texturize function, could possible be consolidated
3209        $textarr = preg_split('/(<.*>)/U', $text, -1, PREG_SPLIT_DELIM_CAPTURE); // capture the tags as well as in between
3210        $stop    = count($textarr);// loop stuff
3211
3212        // Ignore proessing of specific tags
3213        $tags_to_ignore       = 'code|pre|style|script|textarea';
3214        $ignore_block_element = '';
3215
3216        for ($i = 0; $i < $stop; $i++) {
3217            $content = $textarr[ $i ];
3218
3219            // If we're in an ignore block, wait until we find its closing tag
3220            if ('' == $ignore_block_element && preg_match('/^<(' . $tags_to_ignore . ')>/', $content, $matches)) {
3221                $ignore_block_element = $matches[1];
3222            }
3223
3224            // If it's not a tag and not in ignore block
3225            if ('' == $ignore_block_element && strlen($content) > 0 && '<' != $content[0]) {
3226                $content = preg_replace_callback($wp_smiliessearch, 'translate_smiley', $content);
3227            }
3228
3229            // did we exit ignore block
3230            if ('' != $ignore_block_element && '</' . $ignore_block_element . '>' == $content) {
3231                $ignore_block_element = '';
3232            }
3233
3234            $output .= $content;
3235        }
3236    } else {
3237        // return default text.
3238        $output = $text;
3239    }
3240    return $output;
3241}
3242
3243/**
3244 * Verifies that an email is valid.
3245 *
3246 * Does not grok i18n domains. Not RFC compliant.
3247 *
3248 * @since 0.71
3249 *
3250 * @param string $email      Email address to verify.
3251 * @param bool   $deprecated Deprecated.
3252 * @return string|bool Either false or the valid email address.
3253 */
3254function is_email($email, $deprecated = false)
3255{
3256    if (! empty($deprecated)) {
3257        _deprecated_argument(__FUNCTION__, '3.0.0');
3258    }
3259
3260    // Test for the minimum length the email can be
3261    if (strlen($email) < 6) {
3262        /**
3263         * Filters whether an email address is valid.
3264         *
3265         * This filter is evaluated under several different contexts, such as 'email_too_short',
3266         * 'email_no_at', 'local_invalid_chars', 'domain_period_sequence', 'domain_period_limits',
3267         * 'domain_no_periods', 'sub_hyphen_limits', 'sub_invalid_chars', or no specific context.
3268         *
3269         * @since 2.8.0
3270         *
3271         * @param bool   $is_email Whether the email address has passed the is_email() checks. Default false.
3272         * @param string $email    The email address being checked.
3273         * @param string $context  Context under which the email was tested.
3274         */
3275        return apply_filters('is_email', false, $email, 'email_too_short');
3276    }
3277
3278    // Test for an @ character after the first position
3279    if (strpos($email, '@', 1) === false) {
3280        /** This filter is documented in wp-includes/formatting.php */
3281        return apply_filters('is_email', false, $email, 'email_no_at');
3282    }
3283
3284    // Split out the local and domain parts
3285    list( $local, $domain ) = explode('@', $email, 2);
3286
3287    // LOCAL PART
3288    // Test for invalid characters
3289    if (! preg_match('/^[a-zA-Z0-9!#$%&\'*+\/=?^_`{|}~\.-]+$/', $local)) {
3290        /** This filter is documented in wp-includes/formatting.php */
3291        return apply_filters('is_email', false, $email, 'local_invalid_chars');
3292    }
3293
3294    // DOMAIN PART
3295    // Test for sequences of periods
3296    if (preg_match('/\.{2,}/', $domain)) {
3297        /** This filter is documented in wp-includes/formatting.php */
3298        return apply_filters('is_email', false, $email, 'domain_period_sequence');
3299    }
3300
3301    // Test for leading and trailing periods and whitespace
3302    if (trim($domain, " \t\n\r\0\x0B.") !== $domain) {
3303        /** This filter is documented in wp-includes/formatting.php */
3304        return apply_filters('is_email', false, $email, 'domain_period_limits');
3305    }
3306
3307    // Split the domain into subs
3308    $subs = explode('.', $domain);
3309
3310    // Assume the domain will have at least two subs
3311    if (2 > count($subs)) {
3312        /** This filter is documented in wp-includes/formatting.php */
3313        return apply_filters('is_email', false, $email, 'domain_no_periods');
3314    }
3315
3316    // Loop through each sub
3317    foreach ($subs as $sub) {
3318        // Test for leading and trailing hyphens and whitespace
3319        if (trim($sub, " \t\n\r\0\x0B-") !== $sub) {
3320            /** This filter is documented in wp-includes/formatting.php */
3321            return apply_filters('is_email', false, $email, 'sub_hyphen_limits');
3322        }
3323
3324        // Test for invalid characters
3325        if (! preg_match('/^[a-z0-9-]+$/i', $sub)) {
3326            /** This filter is documented in wp-includes/formatting.php */
3327            return apply_filters('is_email', false, $email, 'sub_invalid_chars');
3328        }
3329    }
3330
3331    // Congratulations your email made it!
3332    /** This filter is documented in wp-includes/formatting.php */
3333    return apply_filters('is_email', $email, $email, null);
3334}
3335
3336/**
3337 * Convert to ASCII from email subjects.
3338 *
3339 * @since 1.2.0
3340 *
3341 * @param string $string Subject line
3342 * @return string Converted string to ASCII
3343 */
3344function wp_iso_descrambler($string)
3345{
3346    /* this may only work with iso-8859-1, I'm afraid */
3347    if (! preg_match('#\=\?(.+)\?Q\?(.+)\?\=#i', $string, $matches)) {
3348        return $string;
3349    } else {
3350        $subject = str_replace('_', ' ', $matches[2]);
3351        return preg_replace_callback('#\=([0-9a-f]{2})#i', '_wp_iso_convert', $subject);
3352    }
3353}
3354
3355/**
3356 * Helper function to convert hex encoded chars to ASCII
3357 *
3358 * @since 3.1.0
3359 * @access private
3360 *
3361 * @param array $match The preg_replace_callback matches array
3362 * @return string Converted chars
3363 */
3364function _wp_iso_convert($match)
3365{
3366    return chr(hexdec(strtolower($match[1])));
3367}
3368
3369/**
3370 * Returns a date in the GMT equivalent.
3371 *
3372 * Requires and returns a date in the Y-m-d H:i:s format. If there is a
3373 * timezone_string available, the date is assumed to be in that timezone,
3374 * otherwise it simply subtracts the value of the 'gmt_offset' option. Return
3375 * format can be overridden using the $format parameter.
3376 *
3377 * @since 1.2.0
3378 *
3379 * @param string $string The date to be converted.
3380 * @param string $format The format string for the returned date (default is Y-m-d H:i:s)
3381 * @return string GMT version of the date provided.
3382 */
3383function get_gmt_from_date($string, $format = 'Y-m-d H:i:s')
3384{
3385    $tz = get_option('timezone_string');
3386    if ($tz) {
3387        $datetime = date_create($string, new DateTimeZone($tz));
3388        if (! $datetime) {
3389            return gmdate($format, 0);
3390        }
3391        $datetime->setTimezone(new DateTimeZone('UTC'));
3392        $string_gmt = $datetime->format($format);
3393    } else {
3394        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)) {
3395            $datetime = strtotime($string);
3396            if (false === $datetime) {
3397                return gmdate($format, 0);
3398            }
3399            return gmdate($format, $datetime);
3400        }
3401        $string_time = gmmktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
3402        $string_gmt  = gmdate($format, $string_time - get_option('gmt_offset') * HOUR_IN_SECONDS);
3403    }
3404    return $string_gmt;
3405}
3406
3407/**
3408 * Converts a GMT date into the correct format for the blog.
3409 *
3410 * Requires and returns a date in the Y-m-d H:i:s format. If there is a
3411 * timezone_string available, the returned date is in that timezone, otherwise
3412 * it simply adds the value of gmt_offset. Return format can be overridden
3413 * using the $format parameter
3414 *
3415 * @since 1.2.0
3416 *
3417 * @param string $string The date to be converted.
3418 * @param string $format The format string for the returned date (default is Y-m-d H:i:s)
3419 * @return string Formatted date relative to the timezone / GMT offset.
3420 */
3421function get_date_from_gmt($string, $format = 'Y-m-d H:i:s')
3422{
3423    $tz = get_option('timezone_string');
3424    if ($tz) {
3425        $datetime = date_create($string, new DateTimeZone('UTC'));
3426        if (! $datetime) {
3427            return date($format, 0);
3428        }
3429        $datetime->setTimezone(new DateTimeZone($tz));
3430        $string_localtime = $datetime->format($format);
3431    } else {
3432        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)) {
3433            return date($format, 0);
3434        }
3435        $string_time      = gmmktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
3436        $string_localtime = gmdate($format, $string_time + get_option('gmt_offset') * HOUR_IN_SECONDS);
3437    }
3438    return $string_localtime;
3439}
3440
3441/**
3442 * Computes an offset in seconds from an iso8601 timezone.
3443 *
3444 * @since 1.5.0
3445 *
3446 * @param string $timezone Either 'Z' for 0 offset or '±hhmm'.
3447 * @return int|float The offset in seconds.
3448 */
3449function iso8601_timezone_to_offset($timezone)
3450{
3451    // $timezone is either 'Z' or '[+|-]hhmm'
3452    if ($timezone == 'Z') {
3453        $offset = 0;
3454    } else {
3455        $sign    = ( substr($timezone, 0, 1) == '+' ) ? 1 : -1;
3456        $hours   = intval(substr($timezone, 1, 2));
3457        $minutes = intval(substr($timezone, 3, 4)) / 60;
3458        $offset  = $sign * HOUR_IN_SECONDS * ( $hours + $minutes );
3459    }
3460    return $offset;
3461}
3462
3463/**
3464 * Converts an iso8601 date to MySQL DateTime format used by post_date[_gmt].
3465 *
3466 * @since 1.5.0
3467 *
3468 * @param string $date_string Date and time in ISO 8601 format {@link https://en.wikipedia.org/wiki/ISO_8601}.
3469 * @param string $timezone    Optional. If set to GMT returns the time minus gmt_offset. Default is 'user'.
3470 * @return string The date and time in MySQL DateTime format - Y-m-d H:i:s.
3471 */
3472function iso8601_to_datetime($date_string, $timezone = 'user')
3473{
3474    $timezone = strtolower($timezone);
3475
3476    if ($timezone == 'gmt') {
3477        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);
3478
3479        if (! empty($date_bits[7])) { // we have a timezone, so let's compute an offset
3480            $offset = iso8601_timezone_to_offset($date_bits[7]);
3481        } else { // we don't have a timezone, so we assume user local timezone (not server's!)
3482            $offset = HOUR_IN_SECONDS * get_option('gmt_offset');
3483        }
3484
3485        $timestamp  = gmmktime($date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1]);
3486        $timestamp -= $offset;
3487
3488        return gmdate('Y-m-d H:i:s', $timestamp);
3489    } elseif ($timezone == 'user') {
3490        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);
3491    }
3492}
3493
3494/**
3495 * Strips out all characters that are not allowable in an email.
3496 *
3497 * @since 1.5.0
3498 *
3499 * @param string $email Email address to filter.
3500 * @return string Filtered email address.
3501 */
3502function sanitize_email($email)
3503{
3504    // Test for the minimum length the email can be
3505    if (strlen($email) < 6) {
3506        /**
3507         * Filters a sanitized email address.
3508         *
3509         * This filter is evaluated under several contexts, including 'email_too_short',
3510         * 'email_no_at', 'local_invalid_chars', 'domain_period_sequence', 'domain_period_limits',
3511         * 'domain_no_periods', 'domain_no_valid_subs', or no context.
3512         *
3513         * @since 2.8.0
3514         *
3515         * @param string $email   The sanitized email address.
3516         * @param string $email   The email address, as provided to sanitize_email().
3517         * @param string $message A message to pass to the user.
3518         */
3519        return apply_filters('sanitize_email', '', $email, 'email_too_short');
3520    }
3521
3522    // Test for an @ character after the first position
3523    if (strpos($email, '@', 1) === false) {
3524        /** This filter is documented in wp-includes/formatting.php */
3525        return apply_filters('sanitize_email', '', $email, 'email_no_at');
3526    }
3527
3528    // Split out the local and domain parts
3529    list( $local, $domain ) = explode('@', $email, 2);
3530
3531    // LOCAL PART
3532    // Test for invalid characters
3533    $local = preg_replace('/[^a-zA-Z0-9!#$%&\'*+\/=?^_`{|}~\.-]/', '', $local);
3534    if ('' === $local) {
3535        /** This filter is documented in wp-includes/formatting.php */
3536        return apply_filters('sanitize_email', '', $email, 'local_invalid_chars');
3537    }
3538
3539    // DOMAIN PART
3540    // Test for sequences of periods
3541    $domain = preg_replace('/\.{2,}/', '', $domain);
3542    if ('' === $domain) {
3543        /** This filter is documented in wp-includes/formatting.php */
3544        return apply_filters('sanitize_email', '', $email, 'domain_period_sequence');
3545    }
3546
3547    // Test for leading and trailing periods and whitespace
3548    $domain = trim($domain, " \t\n\r\0\x0B.");
3549    if ('' === $domain) {
3550        /** This filter is documented in wp-includes/formatting.php */
3551        return apply_filters('sanitize_email', '', $email, 'domain_period_limits');
3552    }
3553
3554    // Split the domain into subs
3555    $subs = explode('.', $domain);
3556
3557    // Assume the domain will have at least two subs
3558    if (2 > count($subs)) {
3559        /** This filter is documented in wp-includes/formatting.php */
3560        return apply_filters('sanitize_email', '', $email, 'domain_no_periods');
3561    }
3562
3563    // Create an array that will contain valid subs
3564    $new_subs = array();
3565
3566    // Loop through each sub
3567    foreach ($subs as $sub) {
3568        // Test for leading and trailing hyphens
3569        $sub = trim($sub, " \t\n\r\0\x0B-");
3570
3571        // Test for invalid characters
3572        $sub = preg_replace('/[^a-z0-9-]+/i', '', $sub);
3573
3574        // If there's anything left, add it to the valid subs
3575        if ('' !== $sub) {
3576            $new_subs[] = $sub;
3577        }
3578    }
3579
3580    // If there aren't 2 or more valid subs
3581    if (2 > count($new_subs)) {
3582        /** This filter is documented in wp-includes/formatting.php */
3583        return apply_filters('sanitize_email', '', $email, 'domain_no_valid_subs');
3584    }
3585
3586    // Join valid subs into the new domain
3587    $domain = join('.', $new_subs);
3588
3589    // Put the email back together
3590    $email = $local . '@' . $domain;
3591
3592    // Congratulations your email made it!
3593    /** This filter is documented in wp-includes/formatting.php */
3594    return apply_filters('sanitize_email', $email, $email, null);
3595}
3596
3597/**
3598 * Determines the difference between two timestamps.
3599 *
3600 * The difference is returned in a human readable format such as "1 hour",
3601 * "5 mins", "2 days".
3602 *
3603 * @since 1.5.0
3604 *
3605 * @param int $from Unix timestamp from which the difference begins.
3606 * @param int $to   Optional. Unix timestamp to end the time difference. Default becomes time() if not set.
3607 * @return string Human readable time difference.
3608 */
3609function human_time_diff($from, $to = '')
3610{
3611    if (empty($to)) {
3612        $to = time();
3613    }
3614
3615    $diff = (int) abs($to - $from);
3616
3617    if ($diff < HOUR_IN_SECONDS) {
3618        $mins = round($diff / MINUTE_IN_SECONDS);
3619        if ($mins <= 1) {
3620            $mins = 1;
3621        }
3622        /* translators: Time difference between two dates, in minutes (min=minute). 1: Number of minutes */
3623        $since = sprintf(_n('%s min', '%s mins', $mins), $mins);
3624    } elseif ($diff < DAY_IN_SECONDS && $diff >= HOUR_IN_SECONDS) {
3625        $hours = round($diff / HOUR_IN_SECONDS);
3626        if ($hours <= 1) {
3627            $hours = 1;
3628        }
3629        /* translators: Time difference between two dates, in hours. 1: Number of hours */
3630        $since = sprintf(_n('%s hour', '%s hours', $hours), $hours);
3631    } elseif ($diff < WEEK_IN_SECONDS && $diff >= DAY_IN_SECONDS) {
3632        $days = round($diff / DAY_IN_SECONDS);
3633        if ($days <= 1) {
3634            $days = 1;
3635        }
3636        /* translators: Time difference between two dates, in days. 1: Number of days */
3637        $since = sprintf(_n('%s day', '%s days', $days), $days);
3638    } elseif ($diff < MONTH_IN_SECONDS && $diff >= WEEK_IN_SECONDS) {
3639        $weeks = round($diff / WEEK_IN_SECONDS);
3640        if ($weeks <= 1) {
3641            $weeks = 1;
3642        }
3643        /* translators: Time difference between two dates, in weeks. 1: Number of weeks */
3644        $since = sprintf(_n('%s week', '%s weeks', $weeks), $weeks);
3645    } elseif ($diff < YEAR_IN_SECONDS && $diff >= MONTH_IN_SECONDS) {
3646        $months = round($diff / MONTH_IN_SECONDS);
3647        if ($months <= 1) {
3648            $months = 1;
3649        }
3650        /* translators: Time difference between two dates, in months. 1: Number of months */
3651        $since = sprintf(_n('%s month', '%s months', $months), $months);
3652    } elseif ($diff >= YEAR_IN_SECONDS) {
3653        $years = round($diff / YEAR_IN_SECONDS);
3654        if ($years <= 1) {
3655            $years = 1;
3656        }
3657        /* translators: Time difference between two dates, in years. 1: Number of years */
3658        $since = sprintf(_n('%s year', '%s years', $years), $years);
3659    }
3660
3661    /**
3662     * Filters the human readable difference between two timestamps.
3663     *
3664     * @since 4.0.0
3665     *
3666     * @param string $since The difference in human readable text.
3667     * @param int    $diff  The difference in seconds.
3668     * @param int    $from  Unix timestamp from which the difference begins.
3669     * @param int    $to    Unix timestamp to end the time difference.
3670     */
3671    return apply_filters('human_time_diff', $since, $diff, $from, $to);
3672}
3673
3674/**
3675 * Generates an excerpt from the content, if needed.
3676 *
3677 * The excerpt word amount will be 55 words and if the amount is greater than
3678 * that, then the string ' [&hellip;]' will be appended to the excerpt. If the string
3679 * is less than 55 words, then the content will be returned as is.
3680 *
3681 * The 55 word limit can be modified by plugins/themes using the {@see 'excerpt_length'} filter
3682 * The ' [&hellip;]' string can be modified by plugins/themes using the {@see 'excerpt_more'} filter
3683 *
3684 * @since 1.5.0
3685 *
3686 * @param string $text Optional. The excerpt. If set to empty, an excerpt is generated.
3687 * @return string The excerpt.
3688 */
3689function wp_trim_excerpt($text = '')
3690{
3691    $raw_excerpt = $text;
3692    if ('' == $text) {
3693        $text = get_the_content('');
3694
3695        $text = strip_shortcodes($text);
3696
3697        /** This filter is documented in wp-includes/post-template.php */
3698        $text = apply_filters('the_content', $text);
3699        $text = str_replace(']]>', ']]&gt;', $text);
3700
3701        /**
3702         * Filters the number of words in an excerpt.
3703         *
3704         * @since 2.7.0
3705         *
3706         * @param int $number The number of words. Default 55.
3707         */
3708        $excerpt_length = apply_filters('excerpt_length', 55);
3709        /**
3710         * Filters the string in the "more" link displayed after a trimmed excerpt.
3711         *
3712         * @since 2.9.0
3713         *
3714         * @param string $more_string The string shown within the more link.
3715         */
3716        $excerpt_more = apply_filters('excerpt_more', ' ' . '[&hellip;]');
3717        $text         = wp_trim_words($text, $excerpt_length, $excerpt_more);
3718    }
3719    /**
3720     * Filters the trimmed excerpt string.
3721     *
3722     * @since 2.8.0
3723     *
3724     * @param string $text        The trimmed text.
3725     * @param string $raw_excerpt The text prior to trimming.
3726     */
3727    return apply_filters('wp_trim_excerpt', $text, $raw_excerpt);
3728}
3729
3730/**
3731 * Trims text to a certain number of words.
3732 *
3733 * This function is localized. For languages that count 'words' by the individual
3734 * character (such as East Asian languages), the $num_words argument will apply
3735 * to the number of individual characters.
3736 *
3737 * @since 3.3.0
3738 *
3739 * @param string $text      Text to trim.
3740 * @param int    $num_words Number of words. Default 55.
3741 * @param string $more      Optional. What to append if $text needs to be trimmed. Default '&hellip;'.
3742 * @return string Trimmed text.
3743 */
3744function wp_trim_words($text, $num_words = 55, $more = null)
3745{
3746    if (null === $more) {
3747        $more = __('&hellip;');
3748    }
3749
3750    $original_text = $text;
3751    $text          = wp_strip_all_tags($text);
3752
3753    /*
3754     * translators: If your word count is based on single characters (e.g. East Asian characters),
3755     * enter 'characters_excluding_spaces' or 'characters_including_spaces'. Otherwise, enter 'words'.
3756     * Do not translate into your own language.
3757     */
3758    if (strpos(_x('words', 'Word count type. Do not translate!'), 'characters') === 0 && preg_match('/^utf\-?8$/i', get_option('blog_charset'))) {
3759        $text = trim(preg_replace("/[\n\r\t ]+/", ' ', $text), ' ');
3760        preg_match_all('/./u', $text, $words_array);
3761        $words_array = array_slice($words_array[0], 0, $num_words + 1);
3762        $sep         = '';
3763    } else {
3764        $words_array = preg_split("/[\n\r\t ]+/", $text, $num_words + 1, PREG_SPLIT_NO_EMPTY);
3765        $sep         = ' ';
3766    }
3767
3768    if (count($words_array) > $num_words) {
3769        array_pop($words_array);
3770        $text = implode($sep, $words_array);
3771        $text = $text . $more;
3772    } else {
3773        $text = implode($sep, $words_array);
3774    }
3775
3776    /**
3777     * Filters the text content after words have been trimmed.
3778     *
3779     * @since 3.3.0
3780     *
3781     * @param string $text          The trimmed text.
3782     * @param int    $num_words     The number of words to trim the text to. Default 55.
3783     * @param string $more          An optional string to append to the end of the trimmed text, e.g. &hellip;.
3784     * @param string $original_text The text before it was trimmed.
3785     */
3786    return apply_filters('wp_trim_words', $text, $num_words, $more, $original_text);
3787}
3788
3789/**
3790 * Converts named entities into numbered entities.
3791 *
3792 * @since 1.5.1
3793 *
3794 * @param string $text The text within which entities will be converted.
3795 * @return string Text with converted entities.
3796 */
3797function ent2ncr($text)
3798{
3799
3800    /**
3801     * Filters text before named entities are converted into numbered entities.
3802     *
3803     * A non-null string must be returned for the filter to be evaluated.
3804     *
3805     * @since 3.3.0
3806     *
3807     * @param null   $converted_text The text to be converted. Default null.
3808     * @param string $text           The text prior to entity conversion.
3809     */
3810    $filtered = apply_filters('pre_ent2ncr', null, $text);
3811    if (null !== $filtered) {
3812        return $filtered;
3813    }
3814
3815    $to_ncr = array(
3816        '&quot;'     => '&#34;',
3817        '&amp;'      => '&#38;',
3818        '&lt;'       => '&#60;',
3819        '&gt;'       => '&#62;',
3820        '|'          => '&#124;',
3821        '&nbsp;'     => '&#160;',
3822        '&iexcl;'    => '&#161;',
3823        '&cent;'     => '&#162;',
3824        '&pound;'    => '&#163;',
3825        '&curren;'   => '&#164;',
3826        '&yen;'      => '&#165;',
3827        '&brvbar;'   => '&#166;',
3828        '&brkbar;'   => '&#166;',
3829        '&sect;'     => '&#167;',
3830        '&uml;'      => '&#168;',
3831        '&die;'      => '&#168;',
3832        '&copy;'     => '&#169;',
3833        '&ordf;'     => '&#170;',
3834        '&laquo;'    => '&#171;',
3835        '&not;'      => '&#172;',
3836        '&shy;'      => '&#173;',
3837        '&reg;'      => '&#174;',
3838        '&macr;'     => '&#175;',
3839        '&hibar;'    => '&#175;',
3840        '&deg;'      => '&#176;',
3841        '&plusmn;'   => '&#177;',
3842        '&sup2;'     => '&#178;',
3843        '&sup3;'     => '&#179;',
3844        '&acute;'    => '&#180;',
3845        '&micro;'    => '&#181;',
3846        '&para;'     => '&#182;',
3847        '&middot;'   => '&#183;',
3848        '&cedil;'    => '&#184;',
3849        '&sup1;'     => '&#185;',
3850        '&ordm;'     => '&#186;',
3851        '&raquo;'    => '&#187;',
3852        '&frac14;'   => '&#188;',
3853        '&frac12;'   => '&#189;',
3854        '&frac34;'   => '&#190;',
3855        '&iquest;'   => '&#191;',
3856        '&Agrave;'   => '&#192;',
3857        '&Aacute;'   => '&#193;',
3858        '&Acirc;'    => '&#194;',
3859        '&Atilde;'   => '&#195;',
3860        '&Auml;'     => '&#196;',
3861        '&Aring;'    => '&#197;',
3862        '&AElig;'    => '&#198;',
3863        '&Ccedil;'   => '&#199;',
3864        '&Egrave;'   => '&#200;',
3865        '&Eacute;'   => '&#201;',
3866        '&Ecirc;'    => '&#202;',
3867        '&Euml;'     => '&#203;',
3868        '&Igrave;'   => '&#204;',
3869        '&Iacute;'   => '&#205;',
3870        '&Icirc;'    => '&#206;',
3871        '&Iuml;'     => '&#207;',
3872        '&ETH;'      => '&#208;',
3873        '&Ntilde;'   => '&#209;',
3874        '&Ograve;'   => '&#210;',
3875        '&Oacute;'   => '&#211;',
3876        '&Ocirc;'    => '&#212;',
3877        '&Otilde;'   => '&#213;',
3878        '&Ouml;'     => '&#214;',
3879        '&times;'    => '&#215;',
3880        '&Oslash;'   => '&#216;',
3881        '&Ugrave;'   => '&#217;',
3882        '&Uacute;'   => '&#218;',
3883        '&Ucirc;'    => '&#219;',
3884        '&Uuml;'     => '&#220;',
3885        '&Yacute;'   => '&#221;',
3886        '&THORN;'    => '&#222;',
3887        '&szlig;'    => '&#223;',
3888        '&agrave;'   => '&#224;',
3889        '&aacute;'   => '&#225;',
3890        '&acirc;'    => '&#226;',
3891        '&atilde;'   => '&#227;',
3892        '&auml;'     => '&#228;',
3893        '&aring;'    => '&#229;',
3894        '&aelig;'    => '&#230;',
3895        '&ccedil;'   => '&#231;',
3896        '&egrave;'   => '&#232;',
3897        '&eacute;'   => '&#233;',
3898        '&ecirc;'    => '&#234;',
3899        '&euml;'     => '&#235;',
3900        '&igrave;'   => '&#236;',
3901        '&iacute;'   => '&#237;',
3902        '&icirc;'    => '&#238;',
3903        '&iuml;'     => '&#239;',
3904        '&eth;'      => '&#240;',
3905        '&ntilde;'   => '&#241;',
3906        '&ograve;'   => '&#242;',
3907        '&oacute;'   => '&#243;',
3908        '&ocirc;'    => '&#244;',
3909        '&otilde;'   => '&#245;',
3910        '&ouml;'     => '&#246;',
3911        '&divide;'   => '&#247;',
3912        '&oslash;'   => '&#248;',
3913        '&ugrave;'   => '&#249;',
3914        '&uacute;'   => '&#250;',
3915        '&ucirc;'    => '&#251;',
3916        '&uuml;'     => '&#252;',
3917        '&yacute;'   => '&#253;',
3918        '&thorn;'    => '&#254;',
3919        '&yuml;'     => '&#255;',
3920        '&OElig;'    => '&#338;',
3921        '&oelig;'    => '&#339;',
3922        '&Scaron;'   => '&#352;',
3923        '&scaron;'   => '&#353;',
3924        '&Yuml;'     => '&#376;',
3925        '&fnof;'     => '&#402;',
3926        '&circ;'     => '&#710;',
3927        '&tilde;'    => '&#732;',
3928        '&Alpha;'    => '&#913;',
3929        '&Beta;'     => '&#914;',
3930        '&Gamma;'    => '&#915;',
3931        '&Delta;'    => '&#916;',
3932        '&Epsilon;'  => '&#917;',
3933        '&Zeta;'     => '&#918;',
3934        '&Eta;'      => '&#919;',
3935        '&Theta;'    => '&#920;',
3936        '&Iota;'     => '&#921;',
3937        '&Kappa;'    => '&#922;',
3938        '&Lambda;'   => '&#923;',
3939        '&Mu;'       => '&#924;',
3940        '&Nu;'       => '&#925;',
3941        '&Xi;'       => '&#926;',
3942        '&Omicron;'  => '&#927;',
3943        '&Pi;'       => '&#928;',
3944        '&Rho;'      => '&#929;',
3945        '&Sigma;'    => '&#931;',
3946        '&Tau;'      => '&#932;',
3947        '&Upsilon;'  => '&#933;',
3948        '&Phi;'      => '&#934;',
3949        '&Chi;'      => '&#935;',
3950        '&Psi;'      => '&#936;',
3951        '&Omega;'    => '&#937;',
3952        '&alpha;'    => '&#945;',
3953        '&beta;'     => '&#946;',
3954        '&gamma;'    => '&#947;',
3955        '&delta;'    => '&#948;',
3956        '&epsilon;'  => '&#949;',
3957        '&zeta;'     => '&#950;',
3958        '&eta;'      => '&#951;',
3959        '&theta;'    => '&#952;',
3960        '&iota;'     => '&#953;',
3961        '&kappa;'    => '&#954;',
3962        '&lambda;'   => '&#955;',
3963        '&mu;'       => '&#956;',
3964        '&nu;'       => '&#957;',
3965        '&xi;'       => '&#958;',
3966        '&omicron;'  => '&#959;',
3967        '&pi;'       => '&#960;',
3968        '&rho;'      => '&#961;',
3969        '&sigmaf;'   => '&#962;',
3970        '&sigma;'    => '&#963;',
3971        '&tau;'      => '&#964;',
3972        '&upsilon;'  => '&#965;',
3973        '&phi;'      => '&#966;',
3974        '&chi;'      => '&#967;',
3975        '&psi;'      => '&#968;',
3976        '&omega;'    => '&#969;',
3977        '&thetasym;' => '&#977;',
3978        '&upsih;'    => '&#978;',
3979        '&piv;'      => '&#982;',
3980        '&ensp;'     => '&#8194;',
3981        '&emsp;'     => '&#8195;',
3982        '&thinsp;'   => '&#8201;',
3983        '&zwnj;'     => '&#8204;',
3984        '&zwj;'      => '&#8205;',
3985        '&lrm;'      => '&#8206;',
3986        '&rlm;'      => '&#8207;',
3987        '&ndash;'    => '&#8211;',
3988        '&mdash;'    => '&#8212;',
3989        '&lsquo;'    => '&#8216;',
3990        '&rsquo;'    => '&#8217;',
3991        '&sbquo;'    => '&#8218;',
3992        '&ldquo;'    => '&#8220;',
3993        '&rdquo;'    => '&#8221;',
3994        '&bdquo;'    => '&#8222;',
3995        '&dagger;'   => '&#8224;',
3996        '&Dagger;'   => '&#8225;',
3997        '&bull;'     => '&#8226;',
3998        '&hellip;'   => '&#8230;',
3999        '&permil;'   => '&#8240;',
4000        '&prime;'    => '&#8242;',
4001        '&Prime;'    => '&#8243;',
4002        '&lsaquo;'   => '&#8249;',
4003        '&rsaquo;'   => '&#8250;',
4004        '&oline;'    => '&#8254;',
4005        '&frasl;'    => '&#8260;',
4006        '&euro;'     => '&#8364;',
4007        '&image;'    => '&#8465;',
4008        '&weierp;'   => '&#8472;',
4009        '&real;'     => '&#8476;',
4010        '&trade;'    => '&#8482;',
4011        '&alefsym;'  => '&#8501;',
4012        '&crarr;'    => '&#8629;',
4013        '&lArr;'     => '&#8656;',
4014        '&uArr;'     => '&#8657;',
4015        '&rArr;'     => '&#8658;',
4016        '&dArr;'     => '&#8659;',
4017        '&hArr;'     => '&#8660;',
4018        '&forall;'   => '&#8704;',
4019        '&part;'     => '&#8706;',
4020        '&exist;'    => '&#8707;',
4021        '&empty;'    => '&#8709;',
4022        '&nabla;'    => '&#8711;',
4023        '&isin;'     => '&#8712;',
4024        '&notin;'    => '&#8713;',
4025        '&ni;'       => '&#8715;',
4026        '&prod;'     => '&#8719;',
4027        '&sum;'      => '&#8721;',
4028        '&minus;'    => '&#8722;',
4029        '&lowast;'   => '&#8727;',
4030        '&radic;'    => '&#8730;',
4031        '&prop;'     => '&#8733;',
4032        '&infin;'    => '&#8734;',
4033        '&ang;'      => '&#8736;',
4034        '&and;'      => '&#8743;',
4035        '&or;'       => '&#8744;',
4036        '&cap;'      => '&#8745;',
4037        '&cup;'      => '&#8746;',
4038        '&int;'      => '&#8747;',
4039        '&there4;'   => '&#8756;',
4040        '&sim;'      => '&#8764;',
4041        '&cong;'     => '&#8773;',
4042        '&asymp;'    => '&#8776;',
4043        '&ne;'       => '&#8800;',
4044        '&equiv;'    => '&#8801;',
4045        '&le;'       => '&#8804;',
4046        '&ge;'       => '&#8805;',
4047        '&sub;'      => '&#8834;',
4048        '&sup;'      => '&#8835;',
4049        '&nsub;'     => '&#8836;',
4050        '&sube;'     => '&#8838;',
4051        '&supe;'     => '&#8839;',
4052        '&oplus;'    => '&#8853;',
4053        '&otimes;'   => '&#8855;',
4054        '&perp;'     => '&#8869;',
4055        '&sdot;'     => '&#8901;',
4056        '&lceil;'    => '&#8968;',
4057        '&rceil;'    => '&#8969;',
4058        '&lfloor;'   => '&#8970;',
4059        '&rfloor;'   => '&#8971;',
4060        '&lang;'     => '&#9001;',
4061        '&rang;'     => '&#9002;',
4062        '&larr;'     => '&#8592;',
4063        '&uarr;'     => '&#8593;',
4064        '&rarr;'     => '&#8594;',
4065        '&darr;'     => '&#8595;',
4066        '&harr;'     => '&#8596;',
4067        '&loz;'      => '&#9674;',
4068        '&spades;'   => '&#9824;',
4069        '&clubs;'    => '&#9827;',
4070        '&hearts;'   => '&#9829;',
4071        '&diams;'    => '&#9830;',
4072    );
4073
4074    return str_replace(array_keys($to_ncr), array_values($to_ncr), $text);
4075}
4076
4077/**
4078 * Formats text for the editor.
4079 *
4080 * Generally the browsers treat everything inside a textarea as text, but
4081 * it is still a good idea to HTML entity encode `<`, `>` and `&` in the content.
4082 *
4083 * The filter {@see 'format_for_editor'} is applied here. If `$text` is empty the
4084 * filter will be applied to an empty string.
4085 *
4086 * @since 4.3.0
4087 *
4088 * @see _WP_Editors::editor()
4089 *
4090 * @param string $text           The text to be formatted.
4091 * @param string $default_editor The default editor for the current user.
4092 *                               It is usually either 'html' or 'tinymce'.
4093 * @return string The formatted text after filter is applied.
4094 */
4095function format_for_editor($text, $default_editor = null)
4096{
4097    if ($text) {
4098        $text = htmlspecialchars($text, ENT_NOQUOTES, get_option('blog_charset'));
4099    }
4100
4101    /**
4102     * Filters the text after it is formatted for the editor.
4103     *
4104     * @since 4.3.0
4105     *
4106     * @param string $text           The formatted text.
4107     * @param string $default_editor The default editor for the current user.
4108     *                               It is usually either 'html' or 'tinymce'.
4109     */
4110    return apply_filters('format_for_editor', $text, $default_editor);
4111}
4112
4113/**
4114 * Perform a deep string replace operation to ensure the values in $search are no longer present
4115 *
4116 * Repeats the replacement operation until it no longer replaces anything so as to remove "nested" values
4117 * e.g. $subject = '%0%0%0DDD', $search ='%0D', $result ='' rather than the '%0%0DD' that
4118 * str_replace would return
4119 *
4120 * @since 2.8.1
4121 * @access private
4122 *
4123 * @param string|array $search  The value being searched for, otherwise known as the needle.
4124 *                              An array may be used to designate multiple needles.
4125 * @param string       $subject The string being searched and replaced on, otherwise known as the haystack.
4126 * @return string The string with the replaced values.
4127 */
4128function _deep_replace($search, $subject)
4129{
4130    $subject = (string) $subject;
4131
4132    $count = 1;
4133    while ($count) {
4134        $subject = str_replace($search, '', $subject, $count);
4135    }
4136
4137    return $subject;
4138}
4139
4140/**
4141 * Escapes data for use in a MySQL query.
4142 *
4143 * Usually you should prepare queries using wpdb::prepare().
4144 * Sometimes, spot-escaping is required or useful. One example
4145 * is preparing an array for use in an IN clause.
4146 *
4147 * NOTE: Since 4.8.3, '%' characters will be replaced with a placeholder string,
4148 * this prevents certain SQLi attacks from taking place. This change in behaviour
4149 * may cause issues for code that expects the return value of esc_sql() to be useable
4150 * for other purposes.
4151 *
4152 * @since 2.8.0
4153 *
4154 * @global wpdb $wpdb WordPress database abstraction object.
4155 *
4156 * @param string|array $data Unescaped data
4157 * @return string|array Escaped data
4158 */
4159function esc_sql($data)
4160{
4161    global $wpdb;
4162    return $wpdb->_escape($data);
4163}
4164
4165/**
4166 * Checks and cleans a URL.
4167 *
4168 * A number of characters are removed from the URL. If the URL is for displaying
4169 * (the default behaviour) ampersands are also replaced. The {@see 'clean_url'} filter
4170 * is applied to the returned cleaned URL.
4171 *
4172 * @since 2.8.0
4173 *
4174 * @param string $url       The URL to be cleaned.
4175 * @param array  $protocols Optional. An array of acceptable protocols.
4176 *                          Defaults to return value of wp_allowed_protocols()
4177 * @param string $_context  Private. Use esc_url_raw() for database usage.
4178 * @return string The cleaned $url after the {@see 'clean_url'} filter is applied.
4179 */
4180function esc_url($url, $protocols = null, $_context = 'display')
4181{
4182    $original_url = $url;
4183
4184    if ('' == $url) {
4185        return $url;
4186    }
4187
4188    $url = str_replace(' ', '%20', $url);
4189    $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\[\]\\x80-\\xff]|i', '', $url);
4190
4191    if ('' === $url) {
4192        return $url;
4193    }
4194
4195    if (0 !== stripos($url, 'mailto:')) {
4196        $strip = array( '%0d', '%0a', '%0D', '%0A' );
4197        $url   = _deep_replace($strip, $url);
4198    }
4199
4200    $url = str_replace(';//', '://', $url);
4201    /* If the URL doesn't appear to contain a scheme, we
4202     * presume it needs http:// prepended (unless a relative
4203     * link starting with /, # or ? or a php file).
4204     */
4205    if (strpos($url, ':') === false && ! in_array($url[0], array( '/', '#', '?' )) &&
4206        ! preg_match('/^[a-z0-9-]+?\.php/i', $url) ) {
4207        $url = 'http://' . $url;
4208    }
4209
4210    // Replace ampersands and single quotes only when displaying.
4211    if ('display' == $_context) {
4212        $url = wp_kses_normalize_entities($url);
4213        $url = str_replace('&amp;', '&#038;', $url);
4214        $url = str_replace("'", '&#039;', $url);
4215    }
4216
4217    if (( false !== strpos($url, '[') ) || ( false !== strpos($url, ']') )) {
4218        $parsed = wp_parse_url($url);
4219        $front  = '';
4220
4221        if (isset($parsed['scheme'])) {
4222            $front .= $parsed['scheme'] . '://';
4223        } elseif ('/' === $url[0]) {
4224            $front .= '//';
4225        }
4226
4227        if (isset($parsed['user'])) {
4228            $front .= $parsed['user'];
4229        }
4230
4231        if (isset($parsed['pass'])) {
4232            $front .= ':' . $parsed['pass'];
4233        }
4234
4235        if (isset($parsed['user']) || isset($parsed['pass'])) {
4236            $front .= '@';
4237        }
4238
4239        if (isset($parsed['host'])) {
4240            $front .= $parsed['host'];
4241        }
4242
4243        if (isset($parsed['port'])) {
4244            $front .= ':' . $parsed['port'];
4245        }
4246
4247        $end_dirty = str_replace($front, '', $url);
4248        $end_clean = str_replace(array( '[', ']' ), array( '%5B', '%5D' ), $end_dirty);
4249        $url       = str_replace($end_dirty, $end_clean, $url);
4250    }
4251
4252    if ('/' === $url[0]) {
4253        $good_protocol_url = $url;
4254    } else {
4255        if (! is_array($protocols)) {
4256            $protocols = wp_allowed_protocols();
4257        }
4258        $good_protocol_url = wp_kses_bad_protocol($url, $protocols);
4259        if (strtolower($good_protocol_url) != strtolower($url)) {
4260            return '';
4261        }
4262    }
4263
4264    /**
4265     * Filters a string cleaned and escaped for output as a URL.
4266     *
4267     * @since 2.3.0
4268     *
4269     * @param string $good_protocol_url The cleaned URL to be returned.
4270     * @param string $original_url      The URL prior to cleaning.
4271     * @param string $_context          If 'display', replace ampersands and single quotes only.
4272     */
4273    return apply_filters('clean_url', $good_protocol_url, $original_url, $_context);
4274}
4275
4276/**
4277 * Performs esc_url() for database usage.
4278 *
4279 * @since 2.8.0
4280 *
4281 * @param string $url       The URL to be cleaned.
4282 * @param array  $protocols An array of acceptable protocols.
4283 * @return string The cleaned URL.
4284 */
4285function esc_url_raw($url, $protocols = null)
4286{
4287    return esc_url($url, $protocols, 'db');
4288}
4289
4290/**
4291 * Convert entities, while preserving already-encoded entities.
4292 *
4293 * @link https://secure.php.net/htmlentities Borrowed from the PHP Manual user notes.
4294 *
4295 * @since 1.2.2
4296 *
4297 * @param string $myHTML The text to be converted.
4298 * @return string Converted text.
4299 */
4300function htmlentities2($myHTML)
4301{
4302    $translation_table              = get_html_translation_table(HTML_ENTITIES, ENT_QUOTES);
4303    $translation_table[ chr(38) ] = '&';
4304    return preg_replace('/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,3};)/', '&amp;', strtr($myHTML, $translation_table));
4305}
4306
4307/**
4308 * Escape single quotes, htmlspecialchar " < > &, and fix line endings.
4309 *
4310 * Escapes text strings for echoing in JS. It is intended to be used for inline JS
4311 * (in a tag attribute, for example onclick="..."). Note that the strings have to
4312 * be in single quotes. The {@see 'js_escape'} filter is also applied here.
4313 *
4314 * @since 2.8.0
4315 *
4316 * @param string $text The text to be escaped.
4317 * @return string Escaped text.
4318 */
4319function esc_js($text)
4320{
4321    $safe_text = wp_check_invalid_utf8($text);
4322    $safe_text = _wp_specialchars($safe_text, ENT_COMPAT);
4323    $safe_text = preg_replace('/&#(x)?0*(?(1)27|39);?/i', "'", stripslashes($safe_text));
4324    $safe_text = str_replace("\r", '', $safe_text);
4325    $safe_text = str_replace("\n", '\\n', addslashes($safe_text));
4326    /**
4327     * Filters a string cleaned and escaped for output in JavaScript.
4328     *
4329     * Text passed to esc_js() is stripped of invalid or special characters,
4330     * and properly slashed for output.
4331     *
4332     * @since 2.0.6
4333     *
4334     * @param string $safe_text The text after it has been escaped.
4335     * @param string $text      The text prior to being escaped.
4336     */
4337    return apply_filters('js_escape', $safe_text, $text);
4338}
4339
4340/**
4341 * Escaping for HTML blocks.
4342 *
4343 * @since 2.8.0
4344 *
4345 * @param string $text
4346 * @return string
4347 */
4348function esc_html($text)
4349{
4350    $safe_text = wp_check_invalid_utf8($text);
4351    $safe_text = _wp_specialchars($safe_text, ENT_QUOTES);
4352    /**
4353     * Filters a string cleaned and escaped for output in HTML.
4354     *
4355     * Text passed to esc_html() is stripped of invalid or special characters
4356     * before output.
4357     *
4358     * @since 2.8.0
4359     *
4360     * @param string $safe_text The text after it has been escaped.
4361     * @param string $text      The text prior to being escaped.
4362     */
4363    return apply_filters('esc_html', $safe_text, $text);
4364}
4365
4366/**
4367 * Escaping for HTML attributes.
4368 *
4369 * @since 2.8.0
4370 *
4371 * @param string $text
4372 * @return string
4373 */
4374function esc_attr($text)
4375{
4376    $safe_text = wp_check_invalid_utf8($text);
4377    $safe_text = _wp_specialchars($safe_text, ENT_QUOTES);
4378    /**
4379     * Filters a string cleaned and escaped for output in an HTML attribute.
4380     *
4381     * Text passed to esc_attr() is stripped of invalid or special characters
4382     * before output.
4383     *
4384     * @since 2.0.6
4385     *
4386     * @param string $safe_text The text after it has been escaped.
4387     * @param string $text      The text prior to being escaped.
4388     */
4389    return apply_filters('attribute_escape', $safe_text, $text);
4390}
4391
4392/**
4393 * Escaping for textarea values.
4394 *
4395 * @since 3.1.0
4396 *
4397 * @param string $text
4398 * @return string
4399 */
4400function esc_textarea($text)
4401{
4402    $safe_text = htmlspecialchars($text, ENT_QUOTES, get_option('blog_charset'));
4403    /**
4404     * Filters a string cleaned and escaped for output in a textarea element.
4405     *
4406     * @since 3.1.0
4407     *
4408     * @param string $safe_text The text after it has been escaped.
4409     * @param string $text      The text prior to being escaped.
4410     */
4411    return apply_filters('esc_textarea', $safe_text, $text);
4412}
4413
4414/**
4415 * Escape an HTML tag name.
4416 *
4417 * @since 2.5.0
4418 *
4419 * @param string $tag_name
4420 * @return string
4421 */
4422function tag_escape($tag_name)
4423{
4424    $safe_tag = strtolower(preg_replace('/[^a-zA-Z0-9_:]/', '', $tag_name));
4425    /**
4426     * Filters a string cleaned and escaped for output as an HTML tag.
4427     *
4428     * @since 2.8.0
4429     *
4430     * @param string $safe_tag The tag name after it has been escaped.
4431     * @param string $tag_name The text before it was escaped.
4432     */
4433    return apply_filters('tag_escape', $safe_tag, $tag_name);
4434}
4435
4436/**
4437 * Convert full URL paths to absolute paths.
4438 *
4439 * Removes the http or https protocols and the domain. Keeps the path '/' at the
4440 * beginning, so it isn't a true relative link, but from the web root base.
4441 *
4442 * @since 2.1.0
4443 * @since 4.1.0 Support was added for relative URLs.
4444 *
4445 * @param string $link Full URL path.
4446 * @return string Absolute path.
4447 */
4448function wp_make_link_relative($link)
4449{
4450    return preg_replace('|^(https?:)?//[^/]+(/?.*)|i', '$2', $link);
4451}
4452
4453/**
4454 * Sanitises various option values based on the nature of the option.
4455 *
4456 * This is basically a switch statement which will pass $value through a number
4457 * of functions depending on the $option.
4458 *
4459 * @since 2.0.5
4460 *
4461 * @global wpdb $wpdb WordPress database abstraction object.
4462 *
4463 * @param string $option The name of the option.
4464 * @param string $value  The unsanitised value.
4465 * @return string Sanitized value.
4466 */
4467function sanitize_option($option, $value)
4468{
4469    global $wpdb;
4470
4471    $original_value = $value;
4472    $error          = '';
4473
4474    switch ($option) {
4475        case 'admin_email':
4476        case 'new_admin_email':
4477            $value = $wpdb->strip_invalid_text_for_column($wpdb->options, 'option_value', $value);
4478            if (is_wp_error($value)) {
4479                $error = $value->get_error_message();
4480            } else {
4481                $value = sanitize_email($value);
4482                if (! is_email($value)) {
4483                    $error = __('The email address entered did not appear to be a valid email address. Please enter a valid email address.');
4484                }
4485            }
4486            break;
4487
4488        case 'thumbnail_size_w':
4489        case 'thumbnail_size_h':
4490        case 'medium_size_w':
4491        case 'medium_size_h':
4492        case 'medium_large_size_w':
4493        case 'medium_large_size_h':
4494        case 'large_size_w':
4495        case 'large_size_h':
4496        case 'mailserver_port':
4497        case 'comment_max_links':
4498        case 'page_on_front':
4499        case 'page_for_posts':
4500        case 'rss_excerpt_length':
4501        case 'default_category':
4502        case 'default_email_category':
4503        case 'default_link_category':
4504        case 'close_comments_days_old':
4505        case 'comments_per_page':
4506        case 'thread_comments_depth':
4507        case 'users_can_register':
4508        case 'start_of_week':
4509        case 'site_icon':
4510            $value = absint($value);
4511            break;
4512
4513        case 'posts_per_page':
4514        case 'posts_per_rss':
4515            $value = (int) $value;
4516            if (empty($value)) {
4517                $value = 1;
4518            }
4519            if ($value < -1) {
4520                $value = abs($value);
4521            }
4522            break;
4523
4524        case 'default_ping_status':
4525        case 'default_comment_status':
4526            // Options that if not there have 0 value but need to be something like "closed"
4527            if ($value == '0' || $value == '') {
4528                $value = 'closed';
4529            }
4530            break;
4531
4532        case 'blogdescription':
4533        case 'blogname':
4534            $value = $wpdb->strip_invalid_text_for_column($wpdb->options, 'option_value', $value);
4535            if ($value !== $original_value) {
4536                $value = $wpdb->strip_invalid_text_for_column($wpdb->options, 'option_value', wp_encode_emoji($original_value));
4537            }
4538
4539            if (is_wp_error($value)) {
4540                $error = $value->get_error_message();
4541            } else {
4542                $value = esc_html($value);
4543            }
4544            break;
4545
4546        case 'blog_charset':
4547            $value = preg_replace('/[^a-zA-Z0-9_-]/', '', $value); // strips slashes
4548            break;
4549
4550        case 'blog_public':
4551            // This is the value if the settings checkbox is not checked on POST. Don't rely on this.
4552            if (null === $value) {
4553                $value = 1;
4554            } else {
4555                $value = intval($value);
4556            }
4557            break;
4558
4559        case 'date_format':
4560        case 'time_format':
4561        case 'mailserver_url':
4562        case 'mailserver_login':
4563        case 'mailserver_pass':
4564        case 'upload_path':
4565            $value = $wpdb->strip_invalid_text_for_column($wpdb->options, 'option_value', $value);
4566            if (is_wp_error($value)) {
4567                $error = $value->get_error_message();
4568            } else {
4569                $value = strip_tags($value);
4570                $value = wp_kses_data($value);
4571            }
4572            break;
4573
4574        case 'ping_sites':
4575            $value = explode("\n", $value);
4576            $value = array_filter(array_map('trim', $value));
4577            $value = array_filter(array_map('esc_url_raw', $value));
4578            $value = implode("\n", $value);
4579            break;
4580
4581        case 'gmt_offset':
4582            $value = preg_replace('/[^0-9:.-]/', '', $value); // strips slashes
4583            break;
4584
4585        case 'siteurl':
4586            $value = $wpdb->strip_invalid_text_for_column($wpdb->options, 'option_value', $value);
4587            if (is_wp_error($value)) {
4588                $error = $value->get_error_message();
4589            } else {
4590                if (preg_match('#http(s?)://(.+)#i', $value)) {
4591                    $value = esc_url_raw($value);
4592                } else {
4593                    $error = __('The WordPress address you entered did not appear to be a valid URL. Please enter a valid URL.');
4594                }
4595            }
4596            break;
4597
4598        case 'home':
4599            $value = $wpdb->strip_invalid_text_for_column($wpdb->options, 'option_value', $value);
4600            if (is_wp_error($value)) {
4601                $error = $value->get_error_message();
4602            } else {
4603                if (preg_match('#http(s?)://(.+)#i', $value)) {
4604                    $value = esc_url_raw($value);
4605                } else {
4606                    $error = __('The Site address you entered did not appear to be a valid URL. Please enter a valid URL.');
4607                }
4608            }
4609            break;
4610
4611        case 'WPLANG':
4612            $allowed = get_available_languages();
4613            if (! is_multisite() && defined('WPLANG') && '' !== WPLANG && 'en_US' !== WPLANG) {
4614                $allowed[] = WPLANG;
4615            }
4616            if (! in_array($value, $allowed) && ! empty($value)) {
4617                $value = get_option($option);
4618            }
4619            break;
4620
4621        case 'illegal_names':
4622            $value = $wpdb->strip_invalid_text_for_column($wpdb->options, 'option_value', $value);
4623            if (is_wp_error($value)) {
4624                $error = $value->get_error_message();
4625            } else {
4626                if (! is_array($value)) {
4627                    $value = explode(' ', $value);
4628                }
4629
4630                $value = array_values(array_filter(array_map('trim', $value)));
4631
4632                if (! $value) {
4633                    $value = '';
4634                }
4635            }
4636            break;
4637
4638        case 'limited_email_domains':
4639        case 'banned_email_domains':
4640            $value = $wpdb->strip_invalid_text_for_column($wpdb->options, 'option_value', $value);
4641            if (is_wp_error($value)) {
4642                $error = $value->get_error_message();
4643            } else {
4644                if (! is_array($value)) {
4645                    $value = explode("\n", $value);
4646                }
4647
4648                $domains = array_values(array_filter(array_map('trim', $value)));
4649                $value   = array();
4650
4651                foreach ($domains as $domain) {
4652                    if (! preg_match('/(--|\.\.)/', $domain) && preg_match('|^([a-zA-Z0-9-\.])+$|', $domain)) {
4653                        $value[] = $domain;
4654                    }
4655                }
4656                if (! $value) {
4657                    $value = '';
4658                }
4659            }
4660            break;
4661
4662        case 'timezone_string':
4663            $allowed_zones = timezone_identifiers_list();
4664            if (! in_array($value, $allowed_zones) && ! empty($value)) {
4665                $error = __('The timezone you have entered is not valid. Please select a valid timezone.');
4666            }
4667            break;
4668
4669        case 'permalink_structure':
4670        case 'category_base':
4671        case 'tag_base':
4672            $value = $wpdb->strip_invalid_text_for_column($wpdb->options, 'option_value', $value);
4673            if (is_wp_error($value)) {
4674                $error = $value->get_error_message();
4675            } else {
4676                $value = esc_url_raw($value);
4677                $value = str_replace('http://', '', $value);
4678            }
4679
4680            if ('permalink_structure' === $option && '' !== $value && ! preg_match('/%[^\/%]+%/', $value)) {
4681                $error = sprintf(
4682                    /* translators: %s: Codex URL */
4683                    __('A structure tag is required when using custom permalinks. <a href="%s">Learn more</a>'),
4684                    __('https://codex.wordpress.org/Using_Permalinks#Choosing_your_permalink_structure')
4685                );
4686            }
4687            break;
4688
4689        case 'default_role':
4690            if (! get_role($value) && get_role('subscriber')) {
4691                $value = 'subscriber';
4692            }
4693            break;
4694
4695        case 'moderation_keys':
4696        case 'blacklist_keys':
4697            $value = $wpdb->strip_invalid_text_for_column($wpdb->options, 'option_value', $value);
4698            if (is_wp_error($value)) {
4699                $error = $value->get_error_message();
4700            } else {
4701                $value = explode("\n", $value);
4702                $value = array_filter(array_map('trim', $value));
4703                $value = array_unique($value);
4704                $value = implode("\n", $value);
4705            }
4706            break;
4707    }
4708
4709    if (! empty($error)) {
4710        $value = get_option($option);
4711        if (function_exists('add_settings_error')) {
4712            add_settings_error($option, "invalid_{$option}", $error);
4713        }
4714    }
4715
4716    /**
4717     * Filters an option value following sanitization.
4718     *
4719     * @since 2.3.0
4720     * @since 4.3.0 Added the `$original_value` parameter.
4721     *
4722     * @param string $value          The sanitized option value.
4723     * @param string $option         The option name.
4724     * @param string $original_value The original value passed to the function.
4725     */
4726    return apply_filters("sanitize_option_{$option}", $value, $option, $original_value);
4727}
4728
4729/**
4730 * Maps a function to all non-iterable elements of an array or an object.
4731 *
4732 * This is similar to `array_walk_recursive()` but acts upon objects too.
4733 *
4734 * @since 4.4.0
4735 *
4736 * @param mixed    $value    The array, object, or scalar.
4737 * @param callable $callback The function to map onto $value.
4738 * @return mixed The value with the callback applied to all non-arrays and non-objects inside it.
4739 */
4740function map_deep($value, $callback)
4741{
4742    if (is_array($value)) {
4743        foreach ($value as $index => $item) {
4744            $value[ $index ] = map_deep($item, $callback);
4745        }
4746    } elseif (is_object($value)) {
4747        $object_vars = get_object_vars($value);
4748        foreach ($object_vars as $property_name => $property_value) {
4749            $value->$property_name = map_deep($property_value, $callback);
4750        }
4751    } else {
4752        $value = call_user_func($callback, $value);
4753    }
4754
4755    return $value;
4756}
4757
4758/**
4759 * Parses a string into variables to be stored in an array.
4760 *
4761 * Uses {@link https://secure.php.net/parse_str parse_str()} and stripslashes if
4762 * {@link https://secure.php.net/magic_quotes magic_quotes_gpc} is on.
4763 *
4764 * @since 2.2.1
4765 *
4766 * @param string $string The string to be parsed.
4767 * @param array  $array  Variables will be stored in this array.
4768 */
4769function wp_parse_str($string, &$array)
4770{
4771    parse_str($string, $array);
4772    if (get_magic_quotes_gpc()) {
4773        $array = stripslashes_deep($array);
4774    }
4775    /**
4776     * Filters the array of variables derived from a parsed string.
4777     *
4778     * @since 2.3.0
4779     *
4780     * @param array $array The array populated with variables.
4781     */
4782    $array = apply_filters('wp_parse_str', $array);
4783}
4784
4785/**
4786 * Convert lone less than signs.
4787 *
4788 * KSES already converts lone greater than signs.
4789 *
4790 * @since 2.3.0
4791 *
4792 * @param string $text Text to be converted.
4793 * @return string Converted text.
4794 */
4795function wp_pre_kses_less_than($text)
4796{
4797    return preg_replace_callback('%<[^>]*?((?=<)|>|$)%', 'wp_pre_kses_less_than_callback', $text);
4798}
4799
4800/**
4801 * Callback function used by preg_replace.
4802 *
4803 * @since 2.3.0
4804 *
4805 * @param array $matches Populated by matches to preg_replace.
4806 * @return string The text returned after esc_html if needed.
4807 */
4808function wp_pre_kses_less_than_callback($matches)
4809{
4810    if (false === strpos($matches[0], '>')) {
4811        return esc_html($matches[0]);
4812    }
4813    return $matches[0];
4814}
4815
4816/**
4817 * WordPress implementation of PHP sprintf() with filters.
4818 *
4819 * @since 2.5.0
4820 * @link https://secure.php.net/sprintf
4821 *
4822 * @param string $pattern   The string which formatted args are inserted.
4823 * @param mixed  $args ,... Arguments to be formatted into the $pattern string.
4824 * @return string The formatted string.
4825 */
4826function wp_sprintf($pattern)
4827{
4828    $args      = func_get_args();
4829    $len       = strlen($pattern);
4830    $start     = 0;
4831    $result    = '';
4832    $arg_index = 0;
4833    while ($len > $start) {
4834        // Last character: append and break
4835        if (strlen($pattern) - 1 == $start) {
4836            $result .= substr($pattern, -1);
4837            break;
4838        }
4839
4840        // Literal %: append and continue
4841        if (substr($pattern, $start, 2) == '%%') {
4842            $start  += 2;
4843            $result .= '%';
4844            continue;
4845        }
4846
4847        // Get fragment before next %
4848        $end = strpos($pattern, '%', $start + 1);
4849        if (false === $end) {
4850            $end = $len;
4851        }
4852        $fragment = substr($pattern, $start, $end - $start);
4853
4854        // Fragment has a specifier
4855        if ($pattern[ $start ] == '%') {
4856            // Find numbered arguments or take the next one in order
4857            if (preg_match('/^%(\d+)\$/', $fragment, $matches)) {
4858                $arg      = isset($args[ $matches[1] ]) ? $args[ $matches[1] ] : '';
4859                $fragment = str_replace("%{$matches[1]}$", '%', $fragment);
4860            } else {
4861                ++$arg_index;
4862                $arg = isset($args[ $arg_index ]) ? $args[ $arg_index ] : '';
4863            }
4864
4865            /**
4866             * Filters a fragment from the pattern passed to wp_sprintf().
4867             *
4868             * If the fragment is unchanged, then sprintf() will be run on the fragment.
4869             *
4870             * @since 2.5.0
4871             *
4872             * @param string $fragment A fragment from the pattern.
4873             * @param string $arg      The argument.
4874             */
4875            $_fragment = apply_filters('wp_sprintf', $fragment, $arg);
4876            if ($_fragment != $fragment) {
4877                $fragment = $_fragment;
4878            } else {
4879                $fragment = sprintf($fragment, strval($arg));
4880            }
4881        }
4882
4883        // Append to result and move to next fragment
4884        $result .= $fragment;
4885        $start   = $end;
4886    }
4887    return $result;
4888}
4889
4890/**
4891 * Localize list items before the rest of the content.
4892 *
4893 * The '%l' must be at the first characters can then contain the rest of the
4894 * content. The list items will have ', ', ', and', and ' and ' added depending
4895 * on the amount of list items in the $args parameter.
4896 *
4897 * @since 2.5.0
4898 *
4899 * @param string $pattern Content containing '%l' at the beginning.
4900 * @param array  $args    List items to prepend to the content and replace '%l'.
4901 * @return string Localized list items and rest of the content.
4902 */
4903function wp_sprintf_l($pattern, $args)
4904{
4905    // Not a match
4906    if (substr($pattern, 0, 2) != '%l') {
4907        return $pattern;
4908    }
4909
4910    // Nothing to work with
4911    if (empty($args)) {
4912        return '';
4913    }
4914
4915    /**
4916     * Filters the translated delimiters used by wp_sprintf_l().
4917     * Placeholders (%s) are included to assist translators and then
4918     * removed before the array of strings reaches the filter.
4919     *
4920     * Please note: Ampersands and entities should be avoided here.
4921     *
4922     * @since 2.5.0
4923     *
4924     * @param array $delimiters An array of translated delimiters.
4925     */
4926    $l = apply_filters(
4927        'wp_sprintf_l',
4928        array(
4929            /* translators: used to join items in a list with more than 2 items */
4930            'between'          => sprintf(__('%1$s, %2$s'), '', ''),
4931            /* translators: used to join last two items in a list with more than 2 times */
4932            'between_last_two' => sprintf(__('%1$s, and %2$s'), '', ''),
4933            /* translators: used to join items in a list with only 2 items */
4934            'between_only_two' => sprintf(__('%1$s and %2$s'), '', ''),
4935        )
4936    );
4937
4938    $args   = (array) $args;
4939    $result = array_shift($args);
4940    if (count($args) == 1) {
4941        $result .= $l['between_only_two'] . array_shift($args);
4942    }
4943    // Loop when more than two args
4944    $i = count($args);
4945    while ($i) {
4946        $arg = array_shift($args);
4947        $i--;
4948        if (0 == $i) {
4949            $result .= $l['between_last_two'] . $arg;
4950        } else {
4951            $result .= $l['between'] . $arg;
4952        }
4953    }
4954    return $result . substr($pattern, 2);
4955}
4956
4957/**
4958 * Safely extracts not more than the first $count characters from html string.
4959 *
4960 * UTF-8, tags and entities safe prefix extraction. Entities inside will *NOT*
4961 * be counted as one character. For example &amp; will be counted as 4, &lt; as
4962 * 3, etc.
4963 *
4964 * @since 2.5.0
4965 *
4966 * @param string $str   String to get the excerpt from.
4967 * @param int    $count Maximum number of characters to take.
4968 * @param string $more  Optional. What to append if $str needs to be trimmed. Defaults to empty string.
4969 * @return string The excerpt.
4970 */
4971function wp_html_excerpt($str, $count, $more = null)
4972{
4973    if (null === $more) {
4974        $more = '';
4975    }
4976    $str     = wp_strip_all_tags($str, true);
4977    $excerpt = mb_substr($str, 0, $count);
4978    // remove part of an entity at the end
4979    $excerpt = preg_replace('/&[^;\s]{0,6}$/', '', $excerpt);
4980    if ($str != $excerpt) {
4981        $excerpt = trim($excerpt) . $more;
4982    }
4983    return $excerpt;
4984}
4985
4986/**
4987 * Add a Base url to relative links in passed content.
4988 *
4989 * By default it supports the 'src' and 'href' attributes. However this can be
4990 * changed via the 3rd param.
4991 *
4992 * @since 2.7.0
4993 *
4994 * @global string $_links_add_base
4995 *
4996 * @param string $content String to search for links in.
4997 * @param string $base    The base URL to prefix to links.
4998 * @param array  $attrs   The attributes which should be processed.
4999 * @return string The processed content.
5000 */
5001function links_add_base_url($content, $base, $attrs = array( 'src', 'href' ))
5002{
5003    global $_links_add_base;
5004    $_links_add_base = $base;
5005    $attrs           = implode('|', (array) $attrs);
5006    return preg_replace_callback("!($attrs)=(['\"])(.+?)\\2!i", '_links_add_base', $content);
5007}
5008
5009/**
5010 * Callback to add a base url to relative links in passed content.
5011 *
5012 * @since 2.7.0
5013 * @access private
5014 *
5015 * @global string $_links_add_base
5016 *
5017 * @param string $m The matched link.
5018 * @return string The processed link.
5019 */
5020function _links_add_base($m)
5021{
5022    global $_links_add_base;
5023    //1 = attribute name  2 = quotation mark  3 = URL
5024    return $m[1] . '=' . $m[2] .
5025        ( preg_match('#^(\w{1,20}):#', $m[3], $protocol) && in_array($protocol[1], wp_allowed_protocols()) ?
5026            $m[3] :
5027            WP_Http::make_absolute_url($m[3], $_links_add_base)
5028        )
5029        . $m[2];
5030}
5031
5032/**
5033 * Adds a Target attribute to all links in passed content.
5034 *
5035 * This function by default only applies to `<a>` tags, however this can be
5036 * modified by the 3rd param.
5037 *
5038 * *NOTE:* Any current target attributed will be stripped and replaced.
5039 *
5040 * @since 2.7.0
5041 *
5042 * @global string $_links_add_target
5043 *
5044 * @param string $content String to search for links in.
5045 * @param string $target  The Target to add to the links.
5046 * @param array  $tags    An array of tags to apply to.
5047 * @return string The processed content.
5048 */
5049function links_add_target($content, $target = '_blank', $tags = array( 'a' ))
5050{
5051    global $_links_add_target;
5052    $_links_add_target = $target;
5053    $tags              = implode('|', (array) $tags);
5054    return preg_replace_callback("!<($tags)([^>]*)>!i", '_links_add_target', $content);
5055}
5056
5057/**
5058 * Callback to add a target attribute to all links in passed content.
5059 *
5060 * @since 2.7.0
5061 * @access private
5062 *
5063 * @global string $_links_add_target
5064 *
5065 * @param string $m The matched link.
5066 * @return string The processed link.
5067 */
5068function _links_add_target($m)
5069{
5070    global $_links_add_target;
5071    $tag  = $m[1];
5072    $link = preg_replace('|( target=([\'"])(.*?)\2)|i', '', $m[2]);
5073    return '<' . $tag . $link . ' target="' . esc_attr($_links_add_target) . '">';
5074}
5075
5076/**
5077 * Normalize EOL characters and strip duplicate whitespace.
5078 *
5079 * @since 2.7.0
5080 *
5081 * @param string $str The string to normalize.
5082 * @return string The normalized string.
5083 */
5084function normalize_whitespace($str)
5085{
5086    $str = trim($str);
5087    $str = str_replace("\r", "\n", $str);
5088    $str = preg_replace(array( '/\n+/', '/[ \t]+/' ), array( "\n", ' ' ), $str);
5089    return $str;
5090}
5091
5092/**
5093 * Properly strip all HTML tags including script and style
5094 *
5095 * This differs from strip_tags() because it removes the contents of
5096 * the `<script>` and `<style>` tags. E.g. `strip_tags( '<script>something</script>' )`
5097 * will return 'something'. wp_strip_all_tags will return ''
5098 *
5099 * @since 2.9.0
5100 *
5101 * @param string $string        String containing HTML tags
5102 * @param bool   $remove_breaks Optional. Whether to remove left over line breaks and white space chars
5103 * @return string The processed string.
5104 */
5105function wp_strip_all_tags($string, $remove_breaks = false)
5106{
5107    $string = preg_replace('@<(script|style)[^>]*?>.*?</\\1>@si', '', $string);
5108    $string = strip_tags($string);
5109
5110    if ($remove_breaks) {
5111        $string = preg_replace('/[\r\n\t ]+/', ' ', $string);
5112    }
5113
5114    return trim($string);
5115}
5116
5117/**
5118 * Sanitizes a string from user input or from the database.
5119 *
5120 * - Checks for invalid UTF-8,
5121 * - Converts single `<` characters to entities
5122 * - Strips all tags
5123 * - Removes line breaks, tabs, and extra whitespace
5124 * - Strips octets
5125 *
5126 * @since 2.9.0
5127 *
5128 * @see sanitize_textarea_field()
5129 * @see wp_check_invalid_utf8()
5130 * @see wp_strip_all_tags()
5131 *
5132 * @param string $str String to sanitize.
5133 * @return string Sanitized string.
5134 */
5135function sanitize_text_field($str)
5136{
5137    $filtered = _sanitize_text_fields($str, false);
5138
5139    /**
5140     * Filters a sanitized text field string.
5141     *
5142     * @since 2.9.0
5143     *
5144     * @param string $filtered The sanitized string.
5145     * @param string $str      The string prior to being sanitized.
5146     */
5147    return apply_filters('sanitize_text_field', $filtered, $str);
5148}
5149
5150/**
5151 * Sanitizes a multiline string from user input or from the database.
5152 *
5153 * The function is like sanitize_text_field(), but preserves
5154 * new lines (\n) and other whitespace, which are legitimate
5155 * input in textarea elements.
5156 *
5157 * @see sanitize_text_field()
5158 *
5159 * @since 4.7.0
5160 *
5161 * @param string $str String to sanitize.
5162 * @return string Sanitized string.
5163 */
5164function sanitize_textarea_field($str)
5165{
5166    $filtered = _sanitize_text_fields($str, true);
5167
5168    /**
5169     * Filters a sanitized textarea field string.
5170     *
5171     * @since 4.7.0
5172     *
5173     * @param string $filtered The sanitized string.
5174     * @param string $str      The string prior to being sanitized.
5175     */
5176    return apply_filters('sanitize_textarea_field', $filtered, $str);
5177}
5178
5179/**
5180 * Internal helper function to sanitize a string from user input or from the db
5181 *
5182 * @since 4.7.0
5183 * @access private
5184 *
5185 * @param string $str String to sanitize.
5186 * @param bool $keep_newlines optional Whether to keep newlines. Default: false.
5187 * @return string Sanitized string.
5188 */
5189function _sanitize_text_fields($str, $keep_newlines = false)
5190{
5191    $filtered = wp_check_invalid_utf8($str);
5192
5193    if (strpos($filtered, '<') !== false) {
5194        $filtered = wp_pre_kses_less_than($filtered);
5195        // This will strip extra whitespace for us.
5196        $filtered = wp_strip_all_tags($filtered, false);
5197
5198        // Use html entities in a special case to make sure no later
5199        // newline stripping stage could lead to a functional tag
5200        $filtered = str_replace("<\n", "&lt;\n", $filtered);
5201    }
5202
5203    if (! $keep_newlines) {
5204        $filtered = preg_replace('/[\r\n\t ]+/', ' ', $filtered);
5205    }
5206    $filtered = trim($filtered);
5207
5208    $found = false;
5209    while (preg_match('/%[a-f0-9]{2}/i', $filtered, $match