/** * Main WordPress Formatting API. * * Handles many functions for formatting output. * * @package WordPress */ /** * Replaces common plain text characters with formatted entities. * * Returns given text with transformations of quotes into smart quotes, apostrophes, * dashes, ellipses, the trademark symbol, and the multiplication symbol. * * As an example, * * 'cause today's effort makes it worth tomorrow's "holiday" ... * * Becomes: * * ’cause today’s effort makes it worth tomorrow’s “holiday” … * * Code within certain HTML blocks are skipped. * * Do not use this function before the {@see 'init'} action hook; everything will break. * * @since 0.71 * * @global array $wp_cockneyreplace Array of formatted entities for certain common phrases. * @global array $shortcode_tags * * @param string $text The text to be formatted. * @param bool $reset Set to true for unit testing. Translated patterns will reset. * @return string The string replaced with HTML entities. */ function wptexturize( $text, $reset = false ) { global $wp_cockneyreplace, $shortcode_tags; static $static_characters = null, $static_replacements = null, $dynamic_characters = null, $dynamic_replacements = null, $default_no_texturize_tags = null, $default_no_texturize_shortcodes = null, $run_texturize = true, $apos = null, $prime = null, $double_prime = null, $opening_quote = null, $closing_quote = null, $opening_single_quote = null, $closing_single_quote = null, $open_q_flag = '', $open_sq_flag = '', $apos_flag = ''; // If there's nothing to do, just stop. if ( empty( $text ) || false === $run_texturize ) { return $text; } // Set up static variables. Run once only. if ( $reset || ! isset( $static_characters ) ) { /** * Filters whether to skip running wptexturize(). * * Returning false from the filter will effectively short-circuit wptexturize() * and return the original text passed to the function instead. * * The filter runs only once, the first time wptexturize() is called. * * @since 4.0.0 * * @see wptexturize() * * @param bool $run_texturize Whether to short-circuit wptexturize(). */ $run_texturize = apply_filters( 'run_wptexturize', $run_texturize ); if ( false === $run_texturize ) { return $text; } /* translators: Opening curly double quote. */ $opening_quote = _x( '“', 'opening curly double quote' ); /* translators: Closing curly double quote. */ $closing_quote = _x( '”', 'closing curly double quote' ); /* translators: Apostrophe, for example in 'cause or can't. */ $apos = _x( '’', 'apostrophe' ); /* translators: Prime, for example in 9' (nine feet). */ $prime = _x( '′', 'prime' ); /* translators: Double prime, for example in 9" (nine inches). */ $double_prime = _x( '″', 'double prime' ); /* translators: Opening curly single quote. */ $opening_single_quote = _x( '‘', 'opening curly single quote' ); /* translators: Closing curly single quote. */ $closing_single_quote = _x( '’', 'closing curly single quote' ); /* translators: En dash. */ $en_dash = _x( '–', 'en dash' ); /* translators: Em dash. */ $em_dash = _x( '—', 'em dash' ); $default_no_texturize_tags = array( 'pre', 'code', 'kbd', 'style', 'script', 'tt' ); $default_no_texturize_shortcodes = array( 'code' ); // If a plugin has provided an autocorrect array, use it. if ( isset( $wp_cockneyreplace ) ) { $cockney = array_keys( $wp_cockneyreplace ); $cockneyreplace = array_values( $wp_cockneyreplace ); } else { /* * translators: This is a comma-separated list of words that defy the syntax of quotations in normal use, * for example... 'We do not have enough words yet'... is a typical quoted phrase. But when we write * lines of code 'til we have enough of 'em, then we need to insert apostrophes instead of quotes. */ $cockney = explode( ',', _x( "'tain't,'twere,'twas,'tis,'twill,'til,'bout,'nuff,'round,'cause,'em", 'Comma-separated list of words to texturize in your language' ) ); $cockneyreplace = explode( ',', _x( '’tain’t,’twere,’twas,’tis,’twill,’til,’bout,’nuff,’round,’cause,’em', 'Comma-separated list of replacement words in your language' ) ); } $static_characters = array_merge( array( '...', '``', '\'\'', ' (tm)' ), $cockney ); $static_replacements = array_merge( array( '…', $opening_quote, $closing_quote, ' ™' ), $cockneyreplace ); /* * Pattern-based replacements of characters. * Sort the remaining patterns into several arrays for performance tuning. */ $dynamic_characters = array( 'apos' => array(), 'quote' => array(), 'dash' => array(), ); $dynamic_replacements = array( 'apos' => array(), 'quote' => array(), 'dash' => array(), ); $dynamic = array(); $spaces = wp_spaces_regexp(); // '99' and '99" are ambiguous among other patterns; assume it's an abbreviated year at the end of a quotation. if ( "'" !== $apos || "'" !== $closing_single_quote ) { $dynamic[ '/\'(\d\d)\'(?=\Z|[.,:;!?)}\-\]]|>|' . $spaces . ')/' ] = $apos_flag . '$1' . $closing_single_quote; } if ( "'" !== $apos || '"' !== $closing_quote ) { $dynamic[ '/\'(\d\d)"(?=\Z|[.,:;!?)}\-\]]|>|' . $spaces . ')/' ] = $apos_flag . '$1' . $closing_quote; } // '99 '99s '99's (apostrophe) But never '9 or '99% or '999 or '99.0. if ( "'" !== $apos ) { $dynamic['/\'(?=\d\d(?:\Z|(?![%\d]|[.,]\d)))/'] = $apos_flag; } // Quoted numbers like '0.42'. if ( "'" !== $opening_single_quote && "'" !== $closing_single_quote ) { $dynamic[ '/(?<=\A|' . $spaces . ')\'(\d[.,\d]*)\'/' ] = $open_sq_flag . '$1' . $closing_single_quote; } // Single quote at start, or preceded by (, {, <, [, ", -, or spaces. if ( "'" !== $opening_single_quote ) { $dynamic[ '/(?<=\A|[([{"\-]|<|' . $spaces . ')\'/' ] = $open_sq_flag; } // Apostrophe in a word. No spaces, double apostrophes, or other punctuation. if ( "'" !== $apos ) { $dynamic[ '/(?&/\[\]\x00-\x20=]++)@', $text, $matches ); $tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] ); $found_shortcodes = ! empty( $tagnames ); $shortcode_regex = $found_shortcodes ? _get_wptexturize_shortcode_regex( $tagnames ) : ''; $regex = _get_wptexturize_split_regex( $shortcode_regex ); $textarr = preg_split( $regex, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); foreach ( $textarr as &$curl ) { // Only call _wptexturize_pushpop_element if $curl is a delimiter. $first = $curl[0]; if ( '<' === $first ) { if ( str_starts_with( $curl, ''; $quote_pattern = "/$needle(?=\\Z|[.,:;!?)}\\-\\]]|>|" . $spaces . ')/'; $prime_pattern = "/(?<=\\d)$needle/"; $flag_after_digit = "/(?<=\\d)$flag/"; $flag_no_digit = "/(? &$sentence ) { if ( ! str_contains( $sentence, $needle ) ) { continue; } elseif ( 0 !== $key && 0 === substr_count( $sentence, $close_quote ) ) { $sentence = preg_replace( $quote_pattern, $flag, $sentence, -1, $count ); if ( $count > 1 ) { // This sentence appears to have multiple closing quotes. Attempt Vulcan logic. $sentence = preg_replace( $flag_no_digit, $close_quote, $sentence, -1, $count2 ); if ( 0 === $count2 ) { // Try looking for a quote followed by a period. $count2 = substr_count( $sentence, "$flag." ); if ( $count2 > 0 ) { // Assume the rightmost quote-period match is the end of quotation. $pos = strrpos( $sentence, "$flag." ); } else { /* * When all else fails, make the rightmost candidate a closing quote. * This is most likely to be problematic in the context of bug #18549. */ $pos = strrpos( $sentence, $flag ); } $sentence = substr_replace( $sentence, $close_quote, $pos, strlen( $flag ) ); } // Use conventional replacement on any remaining primes and quotes. $sentence = preg_replace( $prime_pattern, $prime, $sentence ); $sentence = preg_replace( $flag_after_digit, $prime, $sentence ); $sentence = str_replace( $flag, $close_quote, $sentence ); } elseif ( 1 === $count ) { // Found only one closing quote candidate, so give it priority over primes. $sentence = str_replace( $flag, $close_quote, $sentence ); $sentence = preg_replace( $prime_pattern, $prime, $sentence ); } else { // No closing quotes found. Just run primes pattern. $sentence = preg_replace( $prime_pattern, $prime, $sentence ); } } else { $sentence = preg_replace( $prime_pattern, $prime, $sentence ); $sentence = preg_replace( $quote_pattern, $close_quote, $sentence ); } if ( '"' === $needle && str_contains( $sentence, '"' ) ) { $sentence = str_replace( '"', $close_quote, $sentence ); } } return implode( $open_quote, $sentences ); } /** * Searches for disabled element tags. Pushes element to stack on tag open * and pops on tag close. * * Assumes first char of `$text` is tag opening and last char is tag closing. * Assumes second char of `$text` is optionally `/` to indicate closing as in ``. * * @since 2.9.0 * @access private * * @param string $text Text to check. Must be a tag like `` or `[shortcode]`. * @param string[] $stack Array of open tag elements. * @param string[] $disabled_elements Array of tag names to match against. Spaces are not allowed in tag names. */ function _wptexturize_pushpop_element( $text, &$stack, $disabled_elements ) { // Is it an opening tag or closing tag? if ( isset( $text[1] ) && '/' !== $text[1] ) { $opening_tag = true; $name_offset = 1; } elseif ( 0 === count( $stack ) ) { // Stack is empty. Just stop. return; } else { $opening_tag = false; $name_offset = 2; } // Parse out the tag name. $space = strpos( $text, ' ' ); if ( false === $space ) { $space = -1; } else { $space -= $name_offset; } $tag = substr( $text, $name_offset, $space ); // Handle disabled tags. if ( in_array( $tag, $disabled_elements, true ) ) { if ( $opening_tag ) { /* * This disables texturize until we find a closing tag of our type * (e.g.
) even if there was invalid nesting before that.
			 *
			 * Example: in the case 
sadsadasd"baba"
* "baba" won't be texturized. */ array_push( $stack, $tag ); } elseif ( end( $stack ) === $tag ) { array_pop( $stack ); } } } /** * Replaces double line breaks with paragraph elements. * * A group of regex replaces used to identify text formatted with newlines and * replace double line breaks with HTML paragraph tags. The remaining line breaks * after conversion become `
` tags, unless `$br` is set to '0' or 'false'. * * @since 0.71 * * @param string $text The text which has to be formatted. * @param bool $br Optional. If set, this will convert all remaining line breaks * after paragraphing. Line breaks within `' === strtolower( $piece ) || '' === strtolower( $piece ) ) ) { --$nested_code_pre; } if ( $nested_code_pre || empty( $piece ) || ( '<' === $piece[0] && ! preg_match( '|^<\s*[\w]{1,20}+://|', $piece ) ) ) { $r .= $piece; continue; } // Long strings might contain expensive edge cases... if ( 10000 < strlen( $piece ) ) { // ...break it up. foreach ( _split_str_by_whitespace( $piece, 2100 ) as $chunk ) { // 2100: Extra room for scheme and leading and trailing parentheses. if ( 2101 < strlen( $chunk ) ) { $r .= $chunk; // Too big, no whitespace: bail. } else { $r .= make_clickable( $chunk ); } } } else { $ret = " $piece "; // Pad with whitespace to simplify the regexes. $url_clickable = '~ ([\\s(<.,;:!?]) # 1: Leading whitespace, or punctuation. ( # 2: URL. [\\w]{1,20}+:// # Scheme and hier-part prefix. (?=\S{1,2000}\s) # Limit to URLs less than about 2000 characters long. [\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]*+ # Non-punctuation URL character. (?: # Unroll the Loop: Only allow punctuation URL character if followed by a non-punctuation URL character. [\'.,;:!?)] # Punctuation URL character. [\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]++ # Non-punctuation URL character. )* ) (\)?) # 3: Trailing closing parenthesis (for parenthesis balancing post processing). (\\.\\w{2,6})? # 4: Allowing file extensions (e.g., .jpg, .png). ~xS'; /* * The regex is a non-anchored pattern and does not have a single fixed starting character. * Tell PCRE to spend more time optimizing since, when used on a page load, it will probably be used several times. */ $ret = preg_replace_callback( $url_clickable, '_make_url_clickable_cb', $ret ); $ret = preg_replace_callback( '#([\s>])((www|ftp)\.[\w\\x80-\\xff\#$%&~/.\-;:=,?@\[\]+]+)#is', '_make_web_ftp_clickable_cb', $ret ); $ret = preg_replace_callback( '#([\s>])([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})#i', '_make_email_clickable_cb', $ret ); $ret = substr( $ret, 1, -1 ); // Remove our whitespace padding. $r .= $ret; } } // Cleanup of accidental links within links. return preg_replace( '#(]+?>|>))]+?>([^>]+?)#i', '$1$3', $r ); } /** * Breaks a string into chunks by splitting at whitespace characters. * * The length of each returned chunk is as close to the specified length goal as possible, * with the caveat that each chunk includes its trailing delimiter. * Chunks longer than the goal are guaranteed to not have any inner whitespace. * * Joining the returned chunks with empty delimiters reconstructs the input string losslessly. * * Input string must have no null characters (or eventual transformations on output chunks must not care about null characters) * * _split_str_by_whitespace( "1234 67890 1234 67890a cd 1234 890 123456789 1234567890a 45678 1 3 5 7 90 ", 10 ) == * array ( * 0 => '1234 67890 ', // 11 characters: Perfect split. * 1 => '1234 ', // 5 characters: '1234 67890a' was too long. * 2 => '67890a cd ', // 10 characters: '67890a cd 1234' was too long. * 3 => '1234 890 ', // 11 characters: Perfect split. * 4 => '123456789 ', // 10 characters: '123456789 1234567890a' was too long. * 5 => '1234567890a ', // 12 characters: Too long, but no inner whitespace on which to split. * 6 => ' 45678 ', // 11 characters: Perfect split. * 7 => '1 3 5 7 90 ', // 11 characters: End of $text. * ); * * @since 3.4.0 * @access private * * @param string $text The string to split. * @param int $goal The desired chunk length. * @return array Numeric array of chunks. */ function _split_str_by_whitespace( $text, $goal ) { $chunks = array(); $string_nullspace = strtr( $text, "\r\n\t\v\f ", "\000\000\000\000\000\000" ); while ( $goal < strlen( $string_nullspace ) ) { $pos = strrpos( substr( $string_nullspace, 0, $goal + 1 ), "\000" ); if ( false === $pos ) { $pos = strpos( $string_nullspace, "\000", $goal + 1 ); if ( false === $pos ) { break; } } $chunks[] = substr( $text, 0, $pos + 1 ); $text = substr( $text, $pos + 1 ); $string_nullspace = substr( $string_nullspace, $pos + 1 ); } if ( $text ) { $chunks[] = $text; } return $chunks; } /** * Callback to add a rel attribute to HTML A element. * * Will remove already existing string before adding to prevent invalidating (X)HTML. * * @since 5.3.0 * * @param array $matches Single match. * @param string $rel The rel attribute to add. * @return string HTML A element with the added rel attribute. */ function wp_rel_callback( $matches, $rel ) { $text = $matches[1]; $atts = wp_kses_hair( $matches[1], wp_allowed_protocols() ); if ( ! empty( $atts['href'] ) && wp_is_internal_link( $atts['href']['value'] ) ) { $rel = trim( str_replace( 'nofollow', '', $rel ) ); } if ( ! empty( $atts['rel'] ) ) { $parts = array_map( 'trim', explode( ' ', $atts['rel']['value'] ) ); $rel_array = array_map( 'trim', explode( ' ', $rel ) ); $parts = array_unique( array_merge( $parts, $rel_array ) ); $rel = implode( ' ', $parts ); unset( $atts['rel'] ); $html = ''; foreach ( $atts as $name => $value ) { if ( isset( $value['vless'] ) && 'y' === $value['vless'] ) { $html .= $name . ' '; } else { $html .= "{$name}=\"" . esc_attr( $value['value'] ) . '" '; } } $text = trim( $html ); } $rel_attr = $rel ? ' rel="' . esc_attr( $rel ) . '"' : ''; return ""; } /** * Adds `rel="nofollow"` string to all HTML A elements in content. * * @since 1.5.0 * * @param string $text Content that may contain HTML A elements. * @return string Converted content. */ function wp_rel_nofollow( $text ) { // This is a pre-save filter, so text is already escaped. $text = stripslashes( $text ); $text = preg_replace_callback( '||i', static function ( $matches ) { return wp_rel_callback( $matches, 'nofollow' ); }, $text ); return wp_slash( $text ); } /** * Callback to add `rel="nofollow"` string to HTML A element. * * @since 2.3.0 * @deprecated 5.3.0 Use wp_rel_callback() * * @param array $matches Single match. * @return string HTML A Element with `rel="nofollow"`. */ function wp_rel_nofollow_callback( $matches ) { return wp_rel_callback( $matches, 'nofollow' ); } /** * Adds `rel="nofollow ugc"` string to all HTML A elements in content. * * @since 5.3.0 * * @param string $text Content that may contain HTML A elements. * @return string Converted content. */ function wp_rel_ugc( $text ) { // This is a pre-save filter, so text is already escaped. $text = stripslashes( $text ); $text = preg_replace_callback( '||i', static function ( $matches ) { return wp_rel_callback( $matches, 'nofollow ugc' ); }, $text ); return wp_slash( $text ); } /** * Adds `rel="noopener"` to all HTML A elements that have a target. * * @since 5.1.0 * @since 5.6.0 Removed 'noreferrer' relationship. * @deprecated 6.7.0 * * @param string $text Content that may contain HTML A elements. * @return string Converted content. */ function wp_targeted_link_rel( $text ) { _deprecated_function( __FUNCTION__, '6.7.0' ); // Don't run (more expensive) regex if no links with targets. if ( stripos( $text, 'target' ) === false || stripos( $text, ']*target\s*=[^>]*)>|i', 'wp_targeted_link_rel_callback', $part ); } $text = ''; for ( $i = 0; $i < count( $html_parts ); $i++ ) { $text .= $html_parts[ $i ]; if ( isset( $extra_parts[ $i ] ) ) { $text .= $extra_parts[ $i ]; } } return $text; } /** * Callback to add `rel="noopener"` string to HTML A element. * * Will not duplicate an existing 'noopener' value to avoid invalidating the HTML. * * @since 5.1.0 * @since 5.6.0 Removed 'noreferrer' relationship. * @deprecated 6.7.0 * * @param array $matches Single match. * @return string HTML A Element with `rel="noopener"` in addition to any existing values. */ function wp_targeted_link_rel_callback( $matches ) { _deprecated_function( __FUNCTION__, '6.7.0' ); $link_html = $matches[1]; $original_link_html = $link_html; // Consider the HTML escaped if there are no unescaped quotes. $is_escaped = ! preg_match( '/(^|[^\\\\])[\'"]/', $link_html ); if ( $is_escaped ) { // Replace only the quotes so that they are parsable by wp_kses_hair(), leave the rest as is. $link_html = preg_replace( '/\\\\([\'"])/', '$1', $link_html ); } $atts = wp_kses_hair( $link_html, wp_allowed_protocols() ); /** * Filters the rel values that are added to links with `target` attribute. * * @since 5.1.0 * * @param string $rel The rel values. * @param string $link_html The matched content of the link tag including all HTML attributes. */ $rel = apply_filters( 'wp_targeted_link_rel', 'noopener', $link_html ); // Return early if no rel values to be added or if no actual target attribute. if ( ! $rel || ! isset( $atts['target'] ) ) { return ""; } if ( isset( $atts['rel'] ) ) { $all_parts = preg_split( '/\s/', "{$atts['rel']['value']} $rel", -1, PREG_SPLIT_NO_EMPTY ); $rel = implode( ' ', array_unique( $all_parts ) ); } $atts['rel']['whole'] = 'rel="' . esc_attr( $rel ) . '"'; $link_html = implode( ' ', array_column( $atts, 'whole' ) ); if ( $is_escaped ) { $link_html = preg_replace( '/[\'"]/', '\\\\$0', $link_html ); } return ""; } /** * Adds all filters modifying the rel attribute of targeted links. * * @since 5.1.0 * @deprecated 6.7.0 */ function wp_init_targeted_link_rel_filters() { _deprecated_function( __FUNCTION__, '6.7.0' ); } /** * Removes all filters modifying the rel attribute of targeted links. * * @since 5.1.0 * @deprecated 6.7.0 */ function wp_remove_targeted_link_rel_filters() { _deprecated_function( __FUNCTION__, '6.7.0' ); } /** * Converts one smiley code to the icon graphic file equivalent. * * Callback handler for convert_smilies(). * * Looks up one smiley code in the $wpsmiliestrans global array and returns an * `` string for that smiley. * * @since 2.8.0 * * @global array $wpsmiliestrans * * @param array $matches Single match. Smiley code to convert to image. * @return string Image string for smiley. */ function translate_smiley( $matches ) { global $wpsmiliestrans; if ( count( $matches ) === 0 ) { return ''; } $smiley = trim( reset( $matches ) ); $img = $wpsmiliestrans[ $smiley ]; $matches = array(); $ext = preg_match( '/\.([^.]+)$/', $img, $matches ) ? strtolower( $matches[1] ) : false; $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp', 'avif' ); // Don't convert smilies that aren't images - they're probably emoji. if ( ! in_array( $ext, $image_exts, true ) ) { return $img; } /** * Filters the Smiley image URL before it's used in the image element. * * @since 2.9.0 * * @param string $smiley_url URL for the smiley image. * @param string $img Filename for the smiley image. * @param string $site_url Site URL, as returned by site_url(). */ $src_url = apply_filters( 'smilies_src', includes_url( "images/smilies/$img" ), $img, site_url() ); return sprintf( '%s', esc_url( $src_url ), esc_attr( $smiley ) ); } /** * Converts text equivalent of smilies to images. * * Will only convert smilies if the option 'use_smilies' is true and the global * used in the function isn't empty. * * @since 0.71 * * @global string|array $wp_smiliessearch * * @param string $text Content to convert smilies from text. * @return string Converted content with text smilies replaced with images. */ function convert_smilies( $text ) { global $wp_smiliessearch; $output = ''; if ( get_option( 'use_smilies' ) && ! empty( $wp_smiliessearch ) ) { // HTML loop taken from texturize function, could possible be consolidated. $textarr = preg_split( '/(<.*>)/U', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); // Capture the tags as well as in between. $stop = count( $textarr ); // Loop stuff. // Ignore processing of specific tags. $tags_to_ignore = 'code|pre|style|script|textarea'; $ignore_block_element = ''; for ( $i = 0; $i < $stop; $i++ ) { $content = $textarr[ $i ]; // If we're in an ignore block, wait until we find its closing tag. if ( '' === $ignore_block_element && preg_match( '/^<(' . $tags_to_ignore . ')[^>]*>/', $content, $matches ) ) { $ignore_block_element = $matches[1]; } // If it's not a tag and not in ignore block. if ( '' === $ignore_block_element && strlen( $content ) > 0 && '<' !== $content[0] ) { $content = preg_replace_callback( $wp_smiliessearch, 'translate_smiley', $content ); } // Did we exit ignore block? if ( '' !== $ignore_block_element && '' === $content ) { $ignore_block_element = ''; } $output .= $content; } } else { // Return default text. $output = $text; } return $output; } /** * Verifies that an email is valid. * * Does not grok i18n domains. Not RFC compliant. * * @since 0.71 * * @param string $email Email address to verify. * @param bool $deprecated Deprecated. * @return string|false Valid email address on success, false on failure. */ function is_email( $email, $deprecated = false ) { if ( ! empty( $deprecated ) ) { _deprecated_argument( __FUNCTION__, '3.0.0' ); } // Test for the minimum length the email can be. if ( strlen( $email ) < 6 ) { /** * Filters whether an email address is valid. * * This filter is evaluated under several different contexts, such as 'email_too_short', * 'email_no_at', 'local_invalid_chars', 'domain_period_sequence', 'domain_period_limits', * 'domain_no_periods', 'sub_hyphen_limits', 'sub_invalid_chars', or no specific context. * * @since 2.8.0 * * @param string|false $is_email The email address if successfully passed the is_email() checks, false otherwise. * @param string $email The email address being checked. * @param string $context Context under which the email was tested. */ return apply_filters( 'is_email', false, $email, 'email_too_short' ); } // Test for an @ character after the first position. if ( strpos( $email, '@', 1 ) === false ) { /** This filter is documented in wp-includes/formatting.php */ return apply_filters( 'is_email', false, $email, 'email_no_at' ); } // Split out the local and domain parts. list( $local, $domain ) = explode( '@', $email, 2 ); /* * LOCAL PART * Test for invalid characters. */ if ( ! preg_match( '/^[a-zA-Z0-9!#$%&\'*+\/=?^_`{|}~\.-]+$/', $local ) ) { /** This filter is documented in wp-includes/formatting.php */ return apply_filters( 'is_email', false, $email, 'local_invalid_chars' ); } /* * DOMAIN PART * Test for sequences of periods. */ if ( preg_match( '/\.{2,}/', $domain ) ) { /** This filter is documented in wp-includes/formatting.php */ return apply_filters( 'is_email', false, $email, 'domain_period_sequence' ); } // Test for leading and trailing periods and whitespace. if ( trim( $domain, " \t\n\r\0\x0B." ) !== $domain ) { /** This filter is documented in wp-includes/formatting.php */ return apply_filters( 'is_email', false, $email, 'domain_period_limits' ); } // Split the domain into subs. $subs = explode( '.', $domain ); // Assume the domain will have at least two subs. if ( 2 > count( $subs ) ) { /** This filter is documented in wp-includes/formatting.php */ return apply_filters( 'is_email', false, $email, 'domain_no_periods' ); } // Loop through each sub. foreach ( $subs as $sub ) { // Test for leading and trailing hyphens and whitespace. if ( trim( $sub, " \t\n\r\0\x0B-" ) !== $sub ) { /** This filter is documented in wp-includes/formatting.php */ return apply_filters( 'is_email', false, $email, 'sub_hyphen_limits' ); } // Test for invalid characters. if ( ! preg_match( '/^[a-z0-9-]+$/i', $sub ) ) { /** This filter is documented in wp-includes/formatting.php */ return apply_filters( 'is_email', false, $email, 'sub_invalid_chars' ); } } // Congratulations, your email made it! /** This filter is documented in wp-includes/formatting.php */ return apply_filters( 'is_email', $email, $email, null ); } /** * Converts to ASCII from email subjects. * * @since 1.2.0 * * @param string $subject Subject line. * @return string Converted string to ASCII. */ function wp_iso_descrambler( $subject ) { /* this may only work with iso-8859-1, I'm afraid */ if ( ! preg_match( '#\=\?(.+)\?Q\?(.+)\?\=#i', $subject, $matches ) ) { return $subject; } $subject = str_replace( '_', ' ', $matches[2] ); return preg_replace_callback( '#\=([0-9a-f]{2})#i', '_wp_iso_convert', $subject ); } /** * Helper function to convert hex encoded chars to ASCII. * * @since 3.1.0 * @access private * * @param array $matches The preg_replace_callback matches array. * @return string Converted chars. */ function _wp_iso_convert( $matches ) { return chr( hexdec( strtolower( $matches[1] ) ) ); } /** * Given a date in the timezone of the site, returns that date in UTC. * * Requires and returns a date in the Y-m-d H:i:s format. * Return format can be overridden using the $format parameter. * * @since 1.2.0 * * @param string $date_string The date to be converted, in the timezone of the site. * @param string $format The format string for the returned date. Default 'Y-m-d H:i:s'. * @return string Formatted version of the date, in UTC. */ function get_gmt_from_date( $date_string, $format = 'Y-m-d H:i:s' ) { $datetime = date_create( $date_string, wp_timezone() ); if ( false === $datetime ) { return gmdate( $format, 0 ); } return $datetime->setTimezone( new DateTimeZone( 'UTC' ) )->format( $format ); } /** * Given a date in UTC or GMT timezone, returns that date in the timezone of the site. * * Requires a date in the Y-m-d H:i:s format. * Default return format of 'Y-m-d H:i:s' can be overridden using the `$format` parameter. * * @since 1.2.0 * * @param string $date_string The date to be converted, in UTC or GMT timezone. * @param string $format The format string for the returned date. Default 'Y-m-d H:i:s'. * @return string Formatted version of the date, in the site's timezone. */ function get_date_from_gmt( $date_string, $format = 'Y-m-d H:i:s' ) { $datetime = date_create( $date_string, new DateTimeZone( 'UTC' ) ); if ( false === $datetime ) { return gmdate( $format, 0 ); } return $datetime->setTimezone( wp_timezone() )->format( $format ); } /** * Given an ISO 8601 timezone, returns its UTC offset in seconds. * * @since 1.5.0 * * @param string $timezone Either 'Z' for 0 offset or '±hhmm'. * @return int|float The offset in seconds. */ function iso8601_timezone_to_offset( $timezone ) { // $timezone is either 'Z' or '[+|-]hhmm'. if ( 'Z' === $timezone ) { $offset = 0; } else { $sign = ( str_starts_with( $timezone, '+' ) ) ? 1 : -1; $hours = (int) substr( $timezone, 1, 2 ); $minutes = (int) substr( $timezone, 3, 4 ) / 60; $offset = $sign * HOUR_IN_SECONDS * ( $hours + $minutes ); } return $offset; } /** * Given an ISO 8601 (Ymd\TH:i:sO) date, returns a MySQL DateTime (Y-m-d H:i:s) format used by post_date[_gmt]. * * @since 1.5.0 * * @param string $date_string Date and time in ISO 8601 format {@link https://en.wikipedia.org/wiki/ISO_8601}. * @param string $timezone Optional. If set to 'gmt' returns the result in UTC. Default 'user'. * @return string|false The date and time in MySQL DateTime format - Y-m-d H:i:s, or false on failure. */ function iso8601_to_datetime( $date_string, $timezone = 'user' ) { $timezone = strtolower( $timezone ); $wp_timezone = wp_timezone(); $datetime = date_create( $date_string, $wp_timezone ); // Timezone is ignored if input has one. if ( false === $datetime ) { return false; } if ( 'gmt' === $timezone ) { return $datetime->setTimezone( new DateTimeZone( 'UTC' ) )->format( 'Y-m-d H:i:s' ); } if ( 'user' === $timezone ) { return $datetime->setTimezone( $wp_timezone )->format( 'Y-m-d H:i:s' ); } return false; } /** * Strips out all characters that are not allowable in an email. * * @since 1.5.0 * * @param string $email Email address to filter. * @return string Filtered email address. */ function sanitize_email( $email ) { // Test for the minimum length the email can be. if ( strlen( $email ) < 6 ) { /** * Filters a sanitized email address. * * This filter is evaluated under several contexts, including 'email_too_short', * 'email_no_at', 'local_invalid_chars', 'domain_period_sequence', 'domain_period_limits', * 'domain_no_periods', 'domain_no_valid_subs', or no context. * * @since 2.8.0 * * @param string $sanitized_email The sanitized email address. * @param string $email The email address, as provided to sanitize_email(). * @param string|null $message A message to pass to the user. null if email is sanitized. */ return apply_filters( 'sanitize_email', '', $email, 'email_too_short' ); } // Test for an @ character after the first position. if ( strpos( $email, '@', 1 ) === false ) { /** This filter is documented in wp-includes/formatting.php */ return apply_filters( 'sanitize_email', '', $email, 'email_no_at' ); } // Split out the local and domain parts. list( $local, $domain ) = explode( '@', $email, 2 ); /* * LOCAL PART * Test for invalid characters. */ $local = preg_replace( '/[^a-zA-Z0-9!#$%&\'*+\/=?^_`{|}~\.-]/', '', $local ); if ( '' === $local ) { /** This filter is documented in wp-includes/formatting.php */ return apply_filters( 'sanitize_email', '', $email, 'local_invalid_chars' ); } /* * DOMAIN PART * Test for sequences of periods. */ $domain = preg_replace( '/\.{2,}/', '', $domain ); if ( '' === $domain ) { /** This filter is documented in wp-includes/formatting.php */ return apply_filters( 'sanitize_email', '', $email, 'domain_period_sequence' ); } // Test for leading and trailing periods and whitespace. $domain = trim( $domain, " \t\n\r\0\x0B." ); if ( '' === $domain ) { /** This filter is documented in wp-includes/formatting.php */ return apply_filters( 'sanitize_email', '', $email, 'domain_period_limits' ); } // Split the domain into subs. $subs = explode( '.', $domain ); // Assume the domain will have at least two subs. if ( 2 > count( $subs ) ) { /** This filter is documented in wp-includes/formatting.php */ return apply_filters( 'sanitize_email', '', $email, 'domain_no_periods' ); } // Create an array that will contain valid subs. $new_subs = array(); // Loop through each sub. foreach ( $subs as $sub ) { // Test for leading and trailing hyphens. $sub = trim( $sub, " \t\n\r\0\x0B-" ); // Test for invalid characters. $sub = preg_replace( '/[^a-z0-9-]+/i', '', $sub ); // If there's anything left, add it to the valid subs. if ( '' !== $sub ) { $new_subs[] = $sub; } } // If there aren't 2 or more valid subs. if ( 2 > count( $new_subs ) ) { /** This filter is documented in wp-includes/formatting.php */ return apply_filters( 'sanitize_email', '', $email, 'domain_no_valid_subs' ); } // Join valid subs into the new domain. $domain = implode( '.', $new_subs ); // Put the email back together. $sanitized_email = $local . '@' . $domain; // Congratulations, your email made it! /** This filter is documented in wp-includes/formatting.php */ return apply_filters( 'sanitize_email', $sanitized_email, $email, null ); } /** * Determines the difference between two timestamps. * * The difference is returned in a human-readable format such as "1 hour", * "5 minutes", "2 days". * * @since 1.5.0 * @since 5.3.0 Added support for showing a difference in seconds. * * @param int $from Unix timestamp from which the difference begins. * @param int $to Optional. Unix timestamp to end the time difference. Default becomes time() if not set. * @return string Human-readable time difference. */ function human_time_diff( $from, $to = 0 ) { if ( empty( $to ) ) { $to = time(); } $diff = (int) abs( $to - $from ); if ( $diff < MINUTE_IN_SECONDS ) { $secs = $diff; if ( $secs <= 1 ) { $secs = 1; } /* translators: Time difference between two dates, in seconds. %s: Number of seconds. */ $since = sprintf( _n( '%s second', '%s seconds', $secs ), $secs ); } elseif ( $diff < HOUR_IN_SECONDS && $diff >= MINUTE_IN_SECONDS ) { $mins = round( $diff / MINUTE_IN_SECONDS ); if ( $mins <= 1 ) { $mins = 1; } /* translators: Time difference between two dates, in minutes. %s: Number of minutes. */ $since = sprintf( _n( '%s minute', '%s minutes', $mins ), $mins ); } elseif ( $diff < DAY_IN_SECONDS && $diff >= HOUR_IN_SECONDS ) { $hours = round( $diff / HOUR_IN_SECONDS ); if ( $hours <= 1 ) { $hours = 1; } /* translators: Time difference between two dates, in hours. %s: Number of hours. */ $since = sprintf( _n( '%s hour', '%s hours', $hours ), $hours ); } elseif ( $diff < WEEK_IN_SECONDS && $diff >= DAY_IN_SECONDS ) { $days = round( $diff / DAY_IN_SECONDS ); if ( $days <= 1 ) { $days = 1; } /* translators: Time difference between two dates, in days. %s: Number of days. */ $since = sprintf( _n( '%s day', '%s days', $days ), $days ); } elseif ( $diff < MONTH_IN_SECONDS && $diff >= WEEK_IN_SECONDS ) { $weeks = round( $diff / WEEK_IN_SECONDS ); if ( $weeks <= 1 ) { $weeks = 1; } /* translators: Time difference between two dates, in weeks. %s: Number of weeks. */ $since = sprintf( _n( '%s week', '%s weeks', $weeks ), $weeks ); } elseif ( $diff < YEAR_IN_SECONDS && $diff >= MONTH_IN_SECONDS ) { $months = round( $diff / MONTH_IN_SECONDS ); if ( $months <= 1 ) { $months = 1; } /* translators: Time difference between two dates, in months. %s: Number of months. */ $since = sprintf( _n( '%s month', '%s months', $months ), $months ); } elseif ( $diff >= YEAR_IN_SECONDS ) { $years = round( $diff / YEAR_IN_SECONDS ); if ( $years <= 1 ) { $years = 1; } /* translators: Time difference between two dates, in years. %s: Number of years. */ $since = sprintf( _n( '%s year', '%s years', $years ), $years ); } /** * Filters the human-readable difference between two timestamps. * * @since 4.0.0 * * @param string $since The difference in human-readable text. * @param int $diff The difference in seconds. * @param int $from Unix timestamp from which the difference begins. * @param int $to Unix timestamp to end the time difference. */ return apply_filters( 'human_time_diff', $since, $diff, $from, $to ); } /** * Generates an excerpt from the content, if needed. * * Returns a maximum of 55 words with an ellipsis appended if necessary. * * The 55-word limit can be modified by plugins/themes using the {@see 'excerpt_length'} filter * The ' […]' string can be modified by plugins/themes using the {@see 'excerpt_more'} filter * * @since 1.5.0 * @since 5.2.0 Added the `$post` parameter. * @since 6.3.0 Removes footnotes markup from the excerpt content. * * @param string $text Optional. The excerpt. If set to empty, an excerpt is generated. * @param WP_Post|object|int $post Optional. WP_Post instance or Post ID/object. Default null. * @return string The excerpt. */ function wp_trim_excerpt( $text = '', $post = null ) { $raw_excerpt = $text; if ( '' === trim( $text ) ) { $post = get_post( $post ); $text = get_the_content( '', false, $post ); $text = strip_shortcodes( $text ); $text = excerpt_remove_blocks( $text ); $text = excerpt_remove_footnotes( $text ); /* * Temporarily unhook wp_filter_content_tags() since any tags * within the excerpt are stripped out. Modifying the tags here * is wasteful and can lead to bugs in the image counting logic. */ $filter_image_removed = remove_filter( 'the_content', 'wp_filter_content_tags', 12 ); /* * Temporarily unhook do_blocks() since excerpt_remove_blocks( $text ) * handles block rendering needed for excerpt. */ $filter_block_removed = remove_filter( 'the_content', 'do_blocks', 9 ); /** This filter is documented in wp-includes/post-template.php */ $text = apply_filters( 'the_content', $text ); $text = str_replace( ']]>', ']]>', $text ); // Restore the original filter if removed. if ( $filter_block_removed ) { add_filter( 'the_content', 'do_blocks', 9 ); } /* * Only restore the filter callback if it was removed above. The logic * to unhook and restore only applies on the default priority of 10, * which is generally used for the filter callback in WordPress core. */ if ( $filter_image_removed ) { add_filter( 'the_content', 'wp_filter_content_tags', 12 ); } /* translators: Maximum number of words used in a post excerpt. */ $excerpt_length = (int) _x( '55', 'excerpt_length' ); /** * Filters the maximum number of words in a post excerpt. * * @since 2.7.0 * * @param int $number The maximum number of words. Default 55. */ $excerpt_length = (int) apply_filters( 'excerpt_length', $excerpt_length ); /** * Filters the string in the "more" link displayed after a trimmed excerpt. * * @since 2.9.0 * * @param string $more_string The string shown within the more link. */ $excerpt_more = apply_filters( 'excerpt_more', ' ' . '[…]' ); $text = wp_trim_words( $text, $excerpt_length, $excerpt_more ); } /** * Filters the trimmed excerpt string. * * @since 2.8.0 * * @param string $text The trimmed text. * @param string $raw_excerpt The text prior to trimming. */ return apply_filters( 'wp_trim_excerpt', $text, $raw_excerpt ); } /** * Trims text to a certain number of words. * * This function is localized. For languages that count 'words' by the individual * character (such as East Asian languages), the $num_words argument will apply * to the number of individual characters. * * @since 3.3.0 * * @param string $text Text to trim. * @param int $num_words Number of words. Default 55. * @param string $more Optional. What to append if $text needs to be trimmed. Default '…'. * @return string Trimmed text. */ function wp_trim_words( $text, $num_words = 55, $more = null ) { if ( null === $more ) { $more = __( '…' ); } $original_text = $text; $text = wp_strip_all_tags( $text ); $num_words = (int) $num_words; if ( str_starts_with( wp_get_word_count_type(), 'characters' ) && preg_match( '/^utf\-?8$/i', get_option( 'blog_charset' ) ) ) { $text = trim( preg_replace( "/[\n\r\t ]+/", ' ', $text ), ' ' ); preg_match_all( '/./u', $text, $words_array ); $words_array = array_slice( $words_array[0], 0, $num_words + 1 ); $sep = ''; } else { $words_array = preg_split( "/[\n\r\t ]+/", $text, $num_words + 1, PREG_SPLIT_NO_EMPTY ); $sep = ' '; } if ( count( $words_array ) > $num_words ) { array_pop( $words_array ); $text = implode( $sep, $words_array ); $text = $text . $more; } else { $text = implode( $sep, $words_array ); } /** * Filters the text content after words have been trimmed. * * @since 3.3.0 * * @param string $text The trimmed text. * @param int $num_words The number of words to trim the text to. Default 55. * @param string $more An optional string to append to the end of the trimmed text, e.g. …. * @param string $original_text The text before it was trimmed. */ return apply_filters( 'wp_trim_words', $text, $num_words, $more, $original_text ); } /** * Converts named entities into numbered entities. * * @since 1.5.1 * * @param string $text The text within which entities will be converted. * @return string Text with converted entities. */ function ent2ncr( $text ) { /** * Filters text before named entities are converted into numbered entities. * * A non-null string must be returned for the filter to be evaluated. * * @since 3.3.0 * * @param string|null $converted_text The text to be converted. Default null. * @param string $text The text prior to entity conversion. */ $filtered = apply_filters( 'pre_ent2ncr', null, $text ); if ( null !== $filtered ) { return $filtered; } $to_ncr = array( '"' => '"', '&' => '&', '<' => '<', '>' => '>', '|' => '|', ' ' => ' ', '¡' => '¡', '¢' => '¢', '£' => '£', '¤' => '¤', '¥' => '¥', '¦' => '¦', '&brkbar;' => '¦', '§' => '§', '¨' => '¨', '¨' => '¨', '©' => '©', 'ª' => 'ª', '«' => '«', '¬' => '¬', '­' => '­', '®' => '®', '¯' => '¯', '&hibar;' => '¯', '°' => '°', '±' => '±', '²' => '²', '³' => '³', '´' => '´', 'µ' => 'µ', '¶' => '¶', '·' => '·', '¸' => '¸', '¹' => '¹', 'º' => 'º', '»' => '»', '¼' => '¼', '½' => '½', '¾' => '¾', '¿' => '¿', 'À' => 'À', 'Á' => 'Á', 'Â' => 'Â', 'Ã' => 'Ã', 'Ä' => 'Ä', 'Å' => 'Å', 'Æ' => 'Æ', 'Ç' => 'Ç', 'È' => 'È', 'É' => 'É', 'Ê' => 'Ê', 'Ë' => 'Ë', 'Ì' => 'Ì', 'Í' => 'Í', 'Î' => 'Î', 'Ï' => 'Ï', 'Ð' => 'Ð', 'Ñ' => 'Ñ', 'Ò' => 'Ò', 'Ó' => 'Ó', 'Ô' => 'Ô', 'Õ' => 'Õ', 'Ö' => 'Ö', '×' => '×', 'Ø' => 'Ø', 'Ù' => 'Ù', 'Ú' => 'Ú', 'Û' => 'Û', 'Ü' => 'Ü', 'Ý' => 'Ý', 'Þ' => 'Þ', 'ß' => 'ß', 'à' => 'à', 'á' => 'á', 'â' => 'â', 'ã' => 'ã', 'ä' => 'ä', 'å' => 'å', 'æ' => 'æ', 'ç' => 'ç', 'è' => 'è', 'é' => 'é', 'ê' => 'ê', 'ë' => 'ë', 'ì' => 'ì', 'í' => 'í', 'î' => 'î', 'ï' => 'ï', 'ð' => 'ð', 'ñ' => 'ñ', 'ò' => 'ò', 'ó' => 'ó', 'ô' => 'ô', 'õ' => 'õ', 'ö' => 'ö', '÷' => '÷', 'ø' => 'ø', 'ù' => 'ù', 'ú' => 'ú', 'û' => 'û', 'ü' => 'ü', 'ý' => 'ý', 'þ' => 'þ', 'ÿ' => 'ÿ', 'Œ' => 'Œ', 'œ' => 'œ', 'Š' => 'Š', 'š' => 'š', 'Ÿ' => 'Ÿ', 'ƒ' => 'ƒ', 'ˆ' => 'ˆ', '˜' => '˜', 'Α' => 'Α', 'Β' => 'Β', 'Γ' => 'Γ', 'Δ' => 'Δ', 'Ε' => 'Ε', 'Ζ' => 'Ζ', 'Η' => 'Η', 'Θ' => 'Θ', 'Ι' => 'Ι', 'Κ' => 'Κ', 'Λ' => 'Λ', 'Μ' => 'Μ', 'Ν' => 'Ν', 'Ξ' => 'Ξ', 'Ο' => 'Ο', 'Π' => 'Π', 'Ρ' => 'Ρ', 'Σ' => 'Σ', 'Τ' => 'Τ', 'Υ' => 'Υ', 'Φ' => 'Φ', 'Χ' => 'Χ', 'Ψ' => 'Ψ', 'Ω' => 'Ω', 'α' => 'α', 'β' => 'β', 'γ' => 'γ', 'δ' => 'δ', 'ε' => 'ε', 'ζ' => 'ζ', 'η' => 'η', 'θ' => 'θ', 'ι' => 'ι', 'κ' => 'κ', 'λ' => 'λ', 'μ' => 'μ', 'ν' => 'ν', 'ξ' => 'ξ', 'ο' => 'ο', 'π' => 'π', 'ρ' => 'ρ', 'ς' => 'ς', 'σ' => 'σ', 'τ' => 'τ', 'υ' => 'υ', 'φ' => 'φ', 'χ' => 'χ', 'ψ' => 'ψ', 'ω' => 'ω', 'ϑ' => 'ϑ', 'ϒ' => 'ϒ', 'ϖ' => 'ϖ', ' ' => ' ', ' ' => ' ', ' ' => ' ', '‌' => '‌', '‍' => '‍', '‎' => '‎', '‏' => '‏', '–' => '–', '—' => '—', '‘' => '‘', '’' => '’', '‚' => '‚', '“' => '“', '”' => '”', '„' => '„', '†' => '†', '‡' => '‡', '•' => '•', '…' => '…', '‰' => '‰', '′' => '′', '″' => '″', '‹' => '‹', '›' => '›', '‾' => '‾', '⁄' => '⁄', '€' => '€', 'ℑ' => 'ℑ', '℘' => '℘', 'ℜ' => 'ℜ', '™' => '™', 'ℵ' => 'ℵ', '↵' => '↵', '⇐' => '⇐', '⇑' => '⇑', '⇒' => '⇒', '⇓' => '⇓', '⇔' => '⇔', '∀' => '∀', '∂' => '∂', '∃' => '∃', '∅' => '∅', '∇' => '∇', '∈' => '∈', '∉' => '∉', '∋' => '∋', '∏' => '∏', '∑' => '∑', '−' => '−', '∗' => '∗', '√' => '√', '∝' => '∝', '∞' => '∞', '∠' => '∠', '∧' => '∧', '∨' => '∨', '∩' => '∩', '∪' => '∪', '∫' => '∫', '∴' => '∴', '∼' => '∼', '≅' => '≅', '≈' => '≈', '≠' => '≠', '≡' => '≡', '≤' => '≤', '≥' => '≥', '⊂' => '⊂', '⊃' => '⊃', '⊄' => '⊄', '⊆' => '⊆', '⊇' => '⊇', '⊕' => '⊕', '⊗' => '⊗', '⊥' => '⊥', '⋅' => '⋅', '⌈' => '⌈', '⌉' => '⌉', '⌊' => '⌊', '⌋' => '⌋', '⟨' => '〈', '⟩' => '〉', '←' => '←', '↑' => '↑', '→' => '→', '↓' => '↓', '↔' => '↔', '◊' => '◊', '♠' => '♠', '♣' => '♣', '♥' => '♥', '♦' => '♦', ); return str_replace( array_keys( $to_ncr ), array_values( $to_ncr ), $text ); } /** * Formats text for the editor. * * Generally the browsers treat everything inside a textarea as text, but * it is still a good idea to HTML entity encode `<`, `>` and `&` in the content. * * The filter {@see 'format_for_editor'} is applied here. If `$text` is empty the * filter will be applied to an empty string. * * @since 4.3.0 * * @see _WP_Editors::editor() * * @param string $text The text to be formatted. * @param string $default_editor The default editor for the current user. * It is usually either 'html' or 'tinymce'. * @return string The formatted text after filter is applied. */ function format_for_editor( $text, $default_editor = null ) { if ( $text ) { $text = htmlspecialchars( $text, ENT_NOQUOTES, get_option( 'blog_charset' ) ); } /** * Filters the text after it is formatted for the editor. * * @since 4.3.0 * * @param string $text The formatted text. * @param string $default_editor The default editor for the current user. * It is usually either 'html' or 'tinymce'. */ return apply_filters( 'format_for_editor', $text, $default_editor ); } /** * Performs a deep string replace operation to ensure the values in $search are no longer present. * * Repeats the replacement operation until it no longer replaces anything to remove "nested" values * e.g. $subject = '%0%0%0DDD', $search ='%0D', $result ='' rather than the '%0%0DD' that * str_replace would return * * @since 2.8.1 * @access private * * @param string|array $search The value being searched for, otherwise known as the needle. * An array may be used to designate multiple needles. * @param string $subject The string being searched and replaced on, otherwise known as the haystack. * @return string The string with the replaced values. */ function _deep_replace( $search, $subject ) { $subject = (string) $subject; $count = 1; while ( $count ) { $subject = str_replace( $search, '', $subject, $count ); } return $subject; } /** * Escapes data for use in a MySQL query. * * Usually you should prepare queries using wpdb::prepare(). * Sometimes, spot-escaping is required or useful. One example * is preparing an array for use in an IN clause. * * NOTE: Since 4.8.3, '%' characters will be replaced with a placeholder string, * this prevents certain SQLi attacks from taking place. This change in behavior * may cause issues for code that expects the return value of esc_sql() to be usable * for other purposes. * * @since 2.8.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string|array $data Unescaped data. * @return string|array Escaped data, in the same type as supplied. */ function esc_sql( $data ) { global $wpdb; return $wpdb->_escape( $data ); } /** * Checks and cleans a URL. * * A number of characters are removed from the URL. If the URL is for displaying * (the default behavior) ampersands are also replaced. The {@see 'clean_url'} filter * is applied to the returned cleaned URL. * * @since 2.8.0 * * @param string $url The URL to be cleaned. * @param string[] $protocols Optional. An array of acceptable protocols. * Defaults to return value of wp_allowed_protocols(). * @param string $_context Private. Use sanitize_url() for database usage. * @return string The cleaned URL after the {@see 'clean_url'} filter is applied. * An empty string is returned if `$url` specifies a protocol other than * those in `$protocols`, or if `$url` contains an empty string. */ function esc_url( $url, $protocols = null, $_context = 'display' ) { $original_url = $url; if ( '' === $url ) { return $url; } $url = str_replace( ' ', '%20', ltrim( $url ) ); $url = preg_replace( '|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\[\]\\x80-\\xff]|i', '', $url ); if ( '' === $url ) { return $url; } if ( 0 !== stripos( $url, 'mailto:' ) ) { $strip = array( '%0d', '%0a', '%0D', '%0A' ); $url = _deep_replace( $strip, $url ); } $url = str_replace( ';//', '://', $url ); /* * If the URL doesn't appear to contain a scheme, we presume * it needs http:// prepended (unless it's a relative link * starting with /, # or ?, or a PHP file). */ if ( ! str_contains( $url, ':' ) && ! in_array( $url[0], array( '/', '#', '?' ), true ) && ! preg_match( '/^[a-z0-9-]+?\.php/i', $url ) ) { $url = 'http://' . $url; } // Replace ampersands and single quotes only when displaying. if ( 'display' === $_context ) { $url = wp_kses_normalize_entities( $url ); $url = str_replace( '&', '&', $url ); $url = str_replace( "'", ''', $url ); } if ( str_contains( $url, '[' ) || str_contains( $url, ']' ) ) { $parsed = wp_parse_url( $url ); $front = ''; if ( isset( $parsed['scheme'] ) ) { $front .= $parsed['scheme'] . '://'; } elseif ( '/' === $url[0] ) { $front .= '//'; } if ( isset( $parsed['user'] ) ) { $front .= $parsed['user']; } if ( isset( $parsed['pass'] ) ) { $front .= ':' . $parsed['pass']; } if ( isset( $parsed['user'] ) || isset( $parsed['pass'] ) ) { $front .= '@'; } if ( isset( $parsed['host'] ) ) { $front .= $parsed['host']; } if ( isset( $parsed['port'] ) ) { $front .= ':' . $parsed['port']; } $end_dirty = str_replace( $front, '', $url ); $end_clean = str_replace( array( '[', ']' ), array( '%5B', '%5D' ), $end_dirty ); $url = str_replace( $end_dirty, $end_clean, $url ); } if ( '/' === $url[0] ) { $good_protocol_url = $url; } else { if ( ! is_array( $protocols ) ) { $protocols = wp_allowed_protocols(); } $good_protocol_url = wp_kses_bad_protocol( $url, $protocols ); if ( strtolower( $good_protocol_url ) !== strtolower( $url ) ) { return ''; } } /** * Filters a string cleaned and escaped for output as a URL. * * @since 2.3.0 * * @param string $good_protocol_url The cleaned URL to be returned. * @param string $original_url The URL prior to cleaning. * @param string $_context If 'display', replace ampersands and single quotes only. */ return apply_filters( 'clean_url', $good_protocol_url, $original_url, $_context ); } /** * Sanitizes a URL for database or redirect usage. * * This function is an alias for sanitize_url(). * * @since 2.8.0 * @since 6.1.0 Turned into an alias for sanitize_url(). * * @see sanitize_url() * * @param string $url The URL to be cleaned. * @param string[] $protocols Optional. An array of acceptable protocols. * Defaults to return value of wp_allowed_protocols(). * @return string The cleaned URL after sanitize_url() is run. */ function esc_url_raw( $url, $protocols = null ) { return sanitize_url( $url, $protocols ); } /** * Sanitizes a URL for database or redirect usage. * * @since 2.3.1 * @since 2.8.0 Deprecated in favor of esc_url_raw(). * @since 5.9.0 Restored (un-deprecated). * * @see esc_url() * * @param string $url The URL to be cleaned. * @param string[] $protocols Optional. An array of acceptable protocols. * Defaults to return value of wp_allowed_protocols(). * @return string The cleaned URL after esc_url() is run with the 'db' context. */ function sanitize_url( $url, $protocols = null ) { return esc_url( $url, $protocols, 'db' ); } /** * Converts entities, while preserving already-encoded entities. * * @link https://www.php.net/htmlentities Borrowed from the PHP Manual user notes. * * @since 1.2.2 * * @param string $text The text to be converted. * @return string Converted text. */ function htmlentities2( $text ) { $translation_table = get_html_translation_table( HTML_ENTITIES, ENT_QUOTES ); $translation_table[ chr( 38 ) ] = '&'; return preg_replace( '/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,3};)/', '&', strtr( $text, $translation_table ) ); } /** * Escapes single quotes, `"`, `<`, `>`, `&`, and fixes line endings. * * Escapes text strings for echoing in JS. It is intended to be used for inline JS * (in a tag attribute, for example `onclick="..."`). Note that the strings have to * be in single quotes. The {@see 'js_escape'} filter is also applied here. * * @since 2.8.0 * * @param string $text The text to be escaped. * @return string Escaped text. */ function esc_js( $text ) { $safe_text = wp_check_invalid_utf8( $text ); $safe_text = _wp_specialchars( $safe_text, ENT_COMPAT ); $safe_text = preg_replace( '/&#(x)?0*(?(1)27|39);?/i', "'", stripslashes( $safe_text ) ); $safe_text = str_replace( "\r", '', $safe_text ); $safe_text = str_replace( "\n", '\\n', addslashes( $safe_text ) ); /** * Filters a string cleaned and escaped for output in JavaScript. * * Text passed to esc_js() is stripped of invalid or special characters, * and properly slashed for output. * * @since 2.0.6 * * @param string $safe_text The text after it has been escaped. * @param string $text The text prior to being escaped. */ return apply_filters( 'js_escape', $safe_text, $text ); } /** * Escaping for HTML blocks. * * @since 2.8.0 * * @param string $text * @return string */ function esc_html( $text ) { $safe_text = wp_check_invalid_utf8( $text ); $safe_text = _wp_specialchars( $safe_text, ENT_QUOTES ); /** * Filters a string cleaned and escaped for output in HTML. * * Text passed to esc_html() is stripped of invalid or special characters * before output. * * @since 2.8.0 * * @param string $safe_text The text after it has been escaped. * @param string $text The text prior to being escaped. */ return apply_filters( 'esc_html', $safe_text, $text ); } /** * Escaping for HTML attributes. * * @since 2.8.0 * * @param string $text * @return string */ function esc_attr( $text ) { $safe_text = wp_check_invalid_utf8( $text ); $safe_text = _wp_specialchars( $safe_text, ENT_QUOTES ); /** * Filters a string cleaned and escaped for output in an HTML attribute. * * Text passed to esc_attr() is stripped of invalid or special characters * before output. * * @since 2.0.6 * * @param string $safe_text The text after it has been escaped. * @param string $text The text prior to being escaped. */ return apply_filters( 'attribute_escape', $safe_text, $text ); } /** * Escaping for textarea values. * * @since 3.1.0 * * @param string $text * @return string */ function esc_textarea( $text ) { $safe_text = htmlspecialchars( $text, ENT_QUOTES, get_option( 'blog_charset' ) ); /** * Filters a string cleaned and escaped for output in a textarea element. * * @since 3.1.0 * * @param string $safe_text The text after it has been escaped. * @param string $text The text prior to being escaped. */ return apply_filters( 'esc_textarea', $safe_text, $text ); } /** * Escaping for XML blocks. * * @since 5.5.0 * * @param string $text Text to escape. * @return string Escaped text. */ function esc_xml( $text ) { $safe_text = wp_check_invalid_utf8( $text ); $cdata_regex = '\<\!\[CDATA\[.*?\]\]\>'; $regex = <<(.*?)) # the "anything" matched by the lookahead (?({$cdata_regex})) # the CDATA Section matched by the lookahead | # alternative (?(.*)) # non-CDATA Section /sx EOF; $safe_text = (string) preg_replace_callback( $regex, static function ( $matches ) { if ( ! isset( $matches[0] ) ) { return ''; } if ( isset( $matches['non_cdata'] ) ) { // escape HTML entities in the non-CDATA Section. return _wp_specialchars( $matches['non_cdata'], ENT_XML1 ); } // Return the CDATA Section unchanged, escape HTML entities in the rest. return _wp_specialchars( $matches['non_cdata_followed_by_cdata'], ENT_XML1 ) . $matches['cdata']; }, $safe_text ); /** * Filters a string cleaned and escaped for output in XML. * * Text passed to esc_xml() is stripped of invalid or special characters * before output. HTML named character references are converted to their * equivalent code points. * * @since 5.5.0 * * @param string $safe_text The text after it has been escaped. * @param string $text The text prior to being escaped. */ return apply_filters( 'esc_xml', $safe_text, $text ); } /** * Escapes an HTML tag name. * * @since 2.5.0 * @since 6.5.5 Allow hyphens in tag names (i.e. custom elements). * * @param string $tag_name * @return string */ function tag_escape( $tag_name ) { $safe_tag = strtolower( preg_replace( '/[^a-zA-Z0-9-_:]/', '', $tag_name ) ); /** * Filters a string cleaned and escaped for output as an HTML tag. * * @since 2.8.0 * * @param string $safe_tag The tag name after it has been escaped. * @param string $tag_name The text before it was escaped. */ return apply_filters( 'tag_escape', $safe_tag, $tag_name ); } /** * Converts full URL paths to absolute paths. * * Removes the http or https protocols and the domain. Keeps the path '/' at the * beginning, so it isn't a true relative link, but from the web root base. * * @since 2.1.0 * @since 4.1.0 Support was added for relative URLs. * * @param string $link Full URL path. * @return string Absolute path. */ function wp_make_link_relative( $link ) { return preg_replace( '|^(https?:)?//[^/]+(/?.*)|i', '$2', $link ); } /** * Sanitizes various option values based on the nature of the option. * * This is basically a switch statement which will pass $value through a number * of functions depending on the $option. * * @since 2.0.5 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $option The name of the option. * @param mixed $value The unsanitized value. * @return mixed Sanitized value. */ function sanitize_option( $option, $value ) { global $wpdb; $original_value = $value; $error = null; switch ( $option ) { case 'admin_email': case 'new_admin_email': $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value ); if ( is_wp_error( $value ) ) { $error = $value->get_error_message(); } else { $value = sanitize_email( $value ); if ( ! is_email( $value ) ) { $error = __( 'The email address entered did not appear to be a valid email address. Please enter a valid email address.' ); } } break; case 'thumbnail_size_w': case 'thumbnail_size_h': case 'medium_size_w': case 'medium_size_h': case 'medium_large_size_w': case 'medium_large_size_h': case 'large_size_w': case 'large_size_h': case 'mailserver_port': case 'comment_max_links': case 'page_on_front': case 'page_for_posts': case 'rss_excerpt_length': case 'default_category': case 'default_email_category': case 'default_link_category': case 'close_comments_days_old': case 'comments_per_page': case 'thread_comments_depth': case 'users_can_register': case 'start_of_week': case 'site_icon': case 'fileupload_maxk': $value = absint( $value ); break; case 'posts_per_page': case 'posts_per_rss': $value = (int) $value; if ( empty( $value ) ) { $value = 1; } if ( $value < -1 ) { $value = abs( $value ); } break; case 'default_ping_status': case 'default_comment_status': // Options that if not there have 0 value but need to be something like "closed". if ( '0' === (string) $value || '' === $value ) { $value = 'closed'; } break; case 'blogdescription': case 'blogname': $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value ); if ( $value !== $original_value ) { $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', wp_encode_emoji( $original_value ) ); } if ( is_wp_error( $value ) ) { $error = $value->get_error_message(); } else { $value = esc_html( $value ); } break; case 'blog_charset': if ( is_string( $value ) ) { $value = preg_replace( '/[^a-zA-Z0-9_-]/', '', $value ); // Strips slashes. } else { $value = ''; } break; case 'blog_public': // This is the value if the settings checkbox is not checked on POST. Don't rely on this. if ( null === $value ) { $value = 1; } else { $value = (int) $value; } break; case 'date_format': case 'time_format': case 'mailserver_url': case 'mailserver_login': case 'mailserver_pass': case 'upload_path': $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value ); if ( is_wp_error( $value ) ) { $error = $value->get_error_message(); } else { $value = strip_tags( $value ); $value = wp_kses_data( $value ); } break; case 'ping_sites': $value = explode( "\n", $value ); $value = array_filter( array_map( 'trim', $value ) ); $value = array_filter( array_map( 'sanitize_url', $value ) ); $value = implode( "\n", $value ); break; case 'gmt_offset': if ( is_numeric( $value ) ) { $value = preg_replace( '/[^0-9:.-]/', '', $value ); // Strips slashes. } else { $value = ''; } break; case 'siteurl': $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value ); if ( is_wp_error( $value ) ) { $error = $value->get_error_message(); } else { if ( preg_match( '#http(s?)://(.+)#i', $value ) ) { $value = sanitize_url( $value ); } else { $error = __( 'The WordPress address you entered did not appear to be a valid URL. Please enter a valid URL.' ); } } break; case 'home': $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value ); if ( is_wp_error( $value ) ) { $error = $value->get_error_message(); } else { if ( preg_match( '#http(s?)://(.+)#i', $value ) ) { $value = sanitize_url( $value ); } else { $error = __( 'The Site address you entered did not appear to be a valid URL. Please enter a valid URL.' ); } } break; case 'WPLANG': $allowed = get_available_languages(); if ( ! is_multisite() && defined( 'WPLANG' ) && '' !== WPLANG && 'en_US' !== WPLANG ) { $allowed[] = WPLANG; } if ( ! in_array( $value, $allowed, true ) && ! empty( $value ) ) { $value = get_option( $option ); } break; case 'illegal_names': $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value ); if ( is_wp_error( $value ) ) { $error = $value->get_error_message(); } else { if ( ! is_array( $value ) ) { $value = explode( ' ', $value ); } $value = array_values( array_filter( array_map( 'trim', $value ) ) ); if ( ! $value ) { $value = ''; } } break; case 'limited_email_domains': case 'banned_email_domains': $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value ); if ( is_wp_error( $value ) ) { $error = $value->get_error_message(); } else { if ( ! is_array( $value ) ) { $value = explode( "\n", $value ); } $domains = array_values( array_filter( array_map( 'trim', $value ) ) ); $value = array(); foreach ( $domains as $domain ) { if ( ! preg_match( '/(--|\.\.)/', $domain ) && preg_match( '|^([a-zA-Z0-9-\.])+$|', $domain ) ) { $value[] = $domain; } } if ( ! $value ) { $value = ''; } } break; case 'timezone_string': $allowed_zones = timezone_identifiers_list( DateTimeZone::ALL_WITH_BC ); if ( ! in_array( $value, $allowed_zones, true ) && ! empty( $value ) ) { $error = __( 'The timezone you have entered is not valid. Please select a valid timezone.' ); } break; case 'permalink_structure': case 'category_base': case 'tag_base': $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value ); if ( is_wp_error( $value ) ) { $error = $value->get_error_message(); } else { $value = sanitize_url( $value ); $value = str_replace( 'http://', '', $value ); } if ( 'permalink_structure' === $option && null === $error && '' !== $value && ! preg_match( '/%[^\/%]+%/', $value ) ) { $error = sprintf( /* translators: %s: Documentation URL. */ __( 'A structure tag is required when using custom permalinks. Learn more' ), __( 'https://wordpress.org/documentation/article/customize-permalinks/#choosing-your-permalink-structure' ) ); } break; case 'default_role': if ( ! get_role( $value ) && get_role( 'subscriber' ) ) { $value = 'subscriber'; } break; case 'moderation_keys': case 'disallowed_keys': $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value ); if ( is_wp_error( $value ) ) { $error = $value->get_error_message(); } else { $value = explode( "\n", $value ); $value = array_filter( array_map( 'trim', $value ) ); $value = array_unique( $value ); $value = implode( "\n", $value ); } break; } if ( null !== $error ) { if ( '' === $error && is_wp_error( $value ) ) { /* translators: 1: Option name, 2: Error code. */ $error = sprintf( __( 'Could not sanitize the %1$s option. Error code: %2$s' ), $option, $value->get_error_code() ); } $value = get_option( $option ); if ( function_exists( 'add_settings_error' ) ) { add_settings_error( $option, "invalid_{$option}", $error ); } } /** * Filters an option value following sanitization. * * @since 2.3.0 * @since 4.3.0 Added the `$original_value` parameter. * * @param mixed $value The sanitized option value. * @param string $option The option name. * @param mixed $original_value The original value passed to the function. */ return apply_filters( "sanitize_option_{$option}", $value, $option, $original_value ); } /** * Maps a function to all non-iterable elements of an array or an object. * * This is similar to `array_walk_recursive()` but acts upon objects too. * * @since 4.4.0 * * @param mixed $value The array, object, or scalar. * @param callable $callback The function to map onto $value. * @return mixed The value with the callback applied to all non-arrays and non-objects inside it. */ function map_deep( $value, $callback ) { if ( is_array( $value ) ) { foreach ( $value as $index => $item ) { $value[ $index ] = map_deep( $item, $callback ); } } elseif ( is_object( $value ) ) { $object_vars = get_object_vars( $value ); foreach ( $object_vars as $property_name => $property_value ) { $value->$property_name = map_deep( $property_value, $callback ); } } else { $value = call_user_func( $callback, $value ); } return $value; } /** * Parses a string into variables to be stored in an array. * * @since 2.2.1 * * @param string $input_string The string to be parsed. * @param array $result Variables will be stored in this array. */ function wp_parse_str( $input_string, &$result ) { parse_str( (string) $input_string, $result ); /** * Filters the array of variables derived from a parsed string. * * @since 2.2.1 * * @param array $result The array populated with variables. */ $result = apply_filters( 'wp_parse_str', $result ); } /** * Converts lone less than signs. * * KSES already converts lone greater than signs. * * @since 2.3.0 * * @param string $content Text to be converted. * @return string Converted text. */ function wp_pre_kses_less_than( $content ) { return preg_replace_callback( '%<[^>]*?((?=<)|>|$)%', 'wp_pre_kses_less_than_callback', $content ); } /** * Callback function used by preg_replace. * * @since 2.3.0 * * @param string[] $matches Populated by matches to preg_replace. * @return string The text returned after esc_html if needed. */ function wp_pre_kses_less_than_callback( $matches ) { if ( ! str_contains( $matches[0], '>' ) ) { return esc_html( $matches[0] ); } return $matches[0]; } /** * Removes non-allowable HTML from parsed block attribute values when filtering * in the post context. * * @since 5.3.1 * * @param string $content Content to be run through KSES. * @param array[]|string $allowed_html An array of allowed HTML elements * and attributes, or a context name * such as 'post'. * @param string[] $allowed_protocols Array of allowed URL protocols. * @return string Filtered text to run through KSES. */ function wp_pre_kses_block_attributes( $content, $allowed_html, $allowed_protocols ) { /* * `filter_block_content` is expected to call `wp_kses`. Temporarily remove * the filter to avoid recursion. */ remove_filter( 'pre_kses', 'wp_pre_kses_block_attributes', 10 ); $content = filter_block_content( $content, $allowed_html, $allowed_protocols ); add_filter( 'pre_kses', 'wp_pre_kses_block_attributes', 10, 3 ); return $content; } /** * WordPress' implementation of PHP sprintf() with filters. * * @since 2.5.0 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter * by adding it to the function signature. * * @link https://www.php.net/sprintf * * @param string $pattern The string which formatted args are inserted. * @param mixed ...$args Arguments to be formatted into the $pattern string. * @return string The formatted string. */ function wp_sprintf( $pattern, ...$args ) { $len = strlen( $pattern ); $start = 0; $result = ''; $arg_index = 0; while ( $len > $start ) { // Last character: append and break. if ( strlen( $pattern ) - 1 === $start ) { $result .= substr( $pattern, -1 ); break; } // Literal %: append and continue. if ( '%%' === substr( $pattern, $start, 2 ) ) { $start += 2; $result .= '%'; continue; } // Get fragment before next %. $end = strpos( $pattern, '%', $start + 1 ); if ( false === $end ) { $end = $len; } $fragment = substr( $pattern, $start, $end - $start ); // Fragment has a specifier. if ( '%' === $pattern[ $start ] ) { // Find numbered arguments or take the next one in order. if ( preg_match( '/^%(\d+)\$/', $fragment, $matches ) ) { $index = $matches[1] - 1; // 0-based array vs 1-based sprintf() arguments. $arg = isset( $args[ $index ] ) ? $args[ $index ] : ''; $fragment = str_replace( "%{$matches[1]}$", '%', $fragment ); } else { $arg = isset( $args[ $arg_index ] ) ? $args[ $arg_index ] : ''; ++$arg_index; } /** * Filters a fragment from the pattern passed to wp_sprintf(). * * If the fragment is unchanged, then sprintf() will be run on the fragment. * * @since 2.5.0 * * @param string $fragment A fragment from the pattern. * @param string $arg The argument. */ $_fragment = apply_filters( 'wp_sprintf', $fragment, $arg ); if ( $_fragment !== $fragment ) { $fragment = $_fragment; } else { $fragment = sprintf( $fragment, (string) $arg ); } } // Append to result and move to next fragment. $result .= $fragment; $start = $end; } return $result; } /** * Localizes list items before the rest of the content. * * The '%l' must be at the first characters can then contain the rest of the * content. The list items will have ', ', ', and', and ' and ' added depending * on the amount of list items in the $args parameter. * * @since 2.5.0 * * @param string $pattern Content containing '%l' at the beginning. * @param array $args List items to prepend to the content and replace '%l'. * @return string Localized list items and rest of the content. */ function wp_sprintf_l( $pattern, $args ) { // Not a match. if ( ! str_starts_with( $pattern, '%l' ) ) { return $pattern; } // Nothing to work with. if ( empty( $args ) ) { return ''; } /** * Filters the translated delimiters used by wp_sprintf_l(). * Placeholders (%s) are included to assist translators and then * removed before the array of strings reaches the filter. * * Please note: Ampersands and entities should be avoided here. * * @since 2.5.0 * * @param array $delimiters An array of translated delimiters. */ $l = apply_filters( 'wp_sprintf_l', array( /* translators: Used to join items in a list with more than 2 items. */ 'between' => sprintf( __( '%1$s, %2$s' ), '', '' ), /* translators: Used to join last two items in a list with more than 2 times. */ 'between_last_two' => sprintf( __( '%1$s, and %2$s' ), '', '' ), /* translators: Used to join items in a list with only 2 items. */ 'between_only_two' => sprintf( __( '%1$s and %2$s' ), '', '' ), ) ); $args = (array) $args; $result = array_shift( $args ); if ( count( $args ) === 1 ) { $result .= $l['between_only_two'] . array_shift( $args ); } // Loop when more than two args. $i = count( $args ); while ( $i ) { $arg = array_shift( $args ); --$i; if ( 0 === $i ) { $result .= $l['between_last_two'] . $arg; } else { $result .= $l['between'] . $arg; } } return $result . substr( $pattern, 2 ); } /** * Safely extracts not more than the first $count characters from HTML string. * * UTF-8, tags and entities safe prefix extraction. Entities inside will *NOT* * be counted as one character. For example & will be counted as 4, < as * 3, etc. * * @since 2.5.0 * * @param string $str String to get the excerpt from. * @param int $count Maximum number of characters to take. * @param string $more Optional. What to append if $str needs to be trimmed. Defaults to empty string. * @return string The excerpt. */ function wp_html_excerpt( $str, $count, $more = null ) { if ( null === $more ) { $more = ''; } $str = wp_strip_all_tags( $str, true ); $excerpt = mb_substr( $str, 0, $count ); // Remove part of an entity at the end. $excerpt = preg_replace( '/&[^;\s]{0,6}$/', '', $excerpt ); if ( $str !== $excerpt ) { $excerpt = trim( $excerpt ) . $more; } return $excerpt; } /** * Adds a base URL to relative links in passed content. * * By default, this function supports the 'src' and 'href' attributes. * However, this can be modified via the `$attrs` parameter. * * @since 2.7.0 * * @global string $_links_add_base * * @param string $content String to search for links in. * @param string $base The base URL to prefix to links. * @param string[] $attrs The attributes which should be processed. * @return string The processed content. */ function links_add_base_url( $content, $base, $attrs = array( 'src', 'href' ) ) { global $_links_add_base; $_links_add_base = $base; $attrs = implode( '|', (array) $attrs ); return preg_replace_callback( "!($attrs)=(['\"])(.+?)\\2!i", '_links_add_base', $content ); } /** * Callback to add a base URL to relative links in passed content. * * @since 2.7.0 * @access private * * @global string $_links_add_base * * @param string $m The matched link. * @return string The processed link. */ function _links_add_base( $m ) { global $_links_add_base; // 1 = attribute name 2 = quotation mark 3 = URL. return $m[1] . '=' . $m[2] . ( preg_match( '#^(\w{1,20}):#', $m[3], $protocol ) && in_array( $protocol[1], wp_allowed_protocols(), true ) ? $m[3] : WP_Http::make_absolute_url( $m[3], $_links_add_base ) ) . $m[2]; } /** * Adds a target attribute to all links in passed content. * * By default, this function only applies to `` tags. * However, this can be modified via the `$tags` parameter. * * *NOTE:* Any current target attribute will be stripped and replaced. * * @since 2.7.0 * * @global string $_links_add_target * * @param string $content String to search for links in. * @param string $target The target to add to the links. * @param string[] $tags An array of tags to apply to. * @return string The processed content. */ function links_add_target( $content, $target = '_blank', $tags = array( 'a' ) ) { global $_links_add_target; $_links_add_target = $target; $tags = implode( '|', (array) $tags ); return preg_replace_callback( "!<($tags)((\s[^>]*)?)>!i", '_links_add_target', $content ); } /** * Callback to add a target attribute to all links in passed content. * * @since 2.7.0 * @access private * * @global string $_links_add_target * * @param string $m The matched link. * @return string The processed link. */ function _links_add_target( $m ) { global $_links_add_target; $tag = $m[1]; $link = preg_replace( '|( target=([\'"])(.*?)\2)|i', '', $m[2] ); return '<' . $tag . $link . ' target="' . esc_attr( $_links_add_target ) . '">'; } /** * Normalizes EOL characters and strips duplicate whitespace. * * @since 2.7.0 * * @param string $str The string to normalize. * @return string The normalized string. */ function normalize_whitespace( $str ) { $str = trim( $str ); $str = str_replace( "\r", "\n", $str ); $str = preg_replace( array( '/\n+/', '/[ \t]+/' ), array( "\n", ' ' ), $str ); return $str; } /** * Properly strips all HTML tags including 'script' and 'style'. * * This differs from strip_tags() because it removes the contents of * the `' )` * will return 'something'. wp_strip_all_tags() will return an empty string. * * @since 2.9.0 * * @param string $text String containing HTML tags * @param bool $remove_breaks Optional. Whether to remove left over line breaks and white space chars * @return string The processed string. */ function wp_strip_all_tags( $text, $remove_breaks = false ) { if ( is_null( $text ) ) { return ''; } if ( ! is_scalar( $text ) ) { /* * To maintain consistency with pre-PHP 8 error levels, * wp_trigger_error() is used to trigger an E_USER_WARNING, * rather than _doing_it_wrong(), which triggers an E_USER_NOTICE. */ wp_trigger_error( '', sprintf( /* translators: 1: The function name, 2: The argument number, 3: The argument name, 4: The expected type, 5: The provided type. */ __( 'Warning: %1$s expects parameter %2$s (%3$s) to be a %4$s, %5$s given.' ), __FUNCTION__, '#1', '$text', 'string', gettype( $text ) ), E_USER_WARNING ); return ''; } $text = preg_replace( '@<(script|style)[^>]*?>.*?@si', '', $text ); $text = strip_tags( $text ); if ( $remove_breaks ) { $text = preg_replace( '/[\r\n\t ]+/', ' ', $text ); } return trim( $text ); } /** * Sanitizes a string from user input or from the database. * * - Checks for invalid UTF-8, * - Converts single `<` characters to entities * - Strips all tags * - Removes line breaks, tabs, and extra whitespace * - Strips percent-encoded characters * * @since 2.9.0 * * @see sanitize_textarea_field() * @see wp_check_invalid_utf8() * @see wp_strip_all_tags() * * @param string $str String to sanitize. * @return string Sanitized string. */ function sanitize_text_field( $str ) { $filtered = _sanitize_text_fields( $str, false ); /** * Filters a sanitized text field string. * * @since 2.9.0 * * @param string $filtered The sanitized string. * @param string $str The string prior to being sanitized. */ return apply_filters( 'sanitize_text_field', $filtered, $str ); } /** * Sanitizes a multiline string from user input or from the database. * * The function is like sanitize_text_field(), but preserves * new lines (\n) and other whitespace, which are legitimate * input in textarea elements. * * @see sanitize_text_field() * * @since 4.7.0 * * @param string $str String to sanitize. * @return string Sanitized string. */ function sanitize_textarea_field( $str ) { $filtered = _sanitize_text_fields( $str, true ); /** * Filters a sanitized textarea field string. * * @since 4.7.0 * * @param string $filtered The sanitized string. * @param string $str The string prior to being sanitized. */ return apply_filters( 'sanitize_textarea_field', $filtered, $str ); } /** * Internal helper function to sanitize a string from user input or from the database. * * @since 4.7.0 * @access private * * @param string $str String to sanitize. * @param bool $keep_newlines Optional. Whether to keep newlines. Default: false. * @return string Sanitized string. */ function _sanitize_text_fields( $str, $keep_newlines = false ) { if ( is_object( $str ) || is_array( $str ) ) { return ''; } $str = (string) $str; $filtered = wp_check_invalid_utf8( $str ); if ( str_contains( $filtered, '<' ) ) { $filtered = wp_pre_kses_less_than( $filtered ); // This will strip extra whitespace for us. $filtered = wp_strip_all_tags( $filtered, false ); /* * Use HTML entities in a special case to make sure that * later newline stripping stages cannot lead to a functional tag. */ $filtered = str_replace( "<\n", "<\n", $filtered ); } if ( ! $keep_newlines ) { $filtered = preg_replace( '/[\r\n\t ]+/', ' ', $filtered ); } $filtered = trim( $filtered ); // Remove percent-encoded characters. $found = false; while ( preg_match( '/%[a-f0-9]{2}/i', $filtered, $match ) ) { $filtered = str_replace( $match[0], '', $filtered ); $found = true; } if ( $found ) { // Strip out the whitespace that may now exist after removing percent-encoded characters. $filtered = trim( preg_replace( '/ +/', ' ', $filtered ) ); } return $filtered; } /** * i18n-friendly version of basename(). * * @since 3.1.0 * * @param string $path A path. * @param string $suffix If the filename ends in suffix this will also be cut off. * @return string */ function wp_basename( $path, $suffix = '' ) { return urldecode( basename( str_replace( array( '%2F', '%5C' ), '/', urlencode( $path ) ), $suffix ) ); } // phpcs:disable WordPress.WP.CapitalPDangit.MisspelledInComment,WordPress.WP.CapitalPDangit.MisspelledInText,WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid -- 8-) /** * Forever eliminate "Wordpress" from the planet (or at least the little bit we can influence). * * Violating our coding standards for a good function name. * * @since 3.0.0 * * @param string $text The text to be modified. * @return string The modified text. */ function capital_P_dangit( $text ) { // Simple replacement for titles. $current_filter = current_filter(); if ( 'the_title' === $current_filter || 'wp_title' === $current_filter ) { return str_replace( 'Wordpress', 'WordPress', $text ); } // Still here? Use the more judicious replacement. static $dblq = false; if ( false === $dblq ) { $dblq = _x( '“', 'opening curly double quote' ); } return str_replace( array( ' Wordpress', '‘Wordpress', $dblq . 'Wordpress', '>Wordpress', '(Wordpress' ), array( ' WordPress', '‘WordPress', $dblq . 'WordPress', '>WordPress', '(WordPress' ), $text ); } // phpcs:enable /** * Sanitizes a mime type * * @since 3.1.3 * * @param string $mime_type Mime type. * @return string Sanitized mime type. */ function sanitize_mime_type( $mime_type ) { $sani_mime_type = preg_replace( '/[^-+*.a-zA-Z0-9\/]/', '', $mime_type ); /** * Filters a mime type following sanitization. * * @since 3.1.3 * * @param string $sani_mime_type The sanitized mime type. * @param string $mime_type The mime type prior to sanitization. */ return apply_filters( 'sanitize_mime_type', $sani_mime_type, $mime_type ); } /** * Sanitizes space or carriage return separated URLs that are used to send trackbacks. * * @since 3.4.0 * * @param string $to_ping Space or carriage return separated URLs * @return string URLs starting with the http or https protocol, separated by a carriage return. */ function sanitize_trackback_urls( $to_ping ) { $urls_to_ping = preg_split( '/[\r\n\t ]/', trim( $to_ping ), -1, PREG_SPLIT_NO_EMPTY ); foreach ( $urls_to_ping as $k => $url ) { if ( ! preg_match( '#^https?://.#i', $url ) ) { unset( $urls_to_ping[ $k ] ); } } $urls_to_ping = array_map( 'sanitize_url', $urls_to_ping ); $urls_to_ping = implode( "\n", $urls_to_ping ); /** * Filters a list of trackback URLs following sanitization. * * The string returned here consists of a space or carriage return-delimited list * of trackback URLs. * * @since 3.4.0 * * @param string $urls_to_ping Sanitized space or carriage return separated URLs. * @param string $to_ping Space or carriage return separated URLs before sanitization. */ return apply_filters( 'sanitize_trackback_urls', $urls_to_ping, $to_ping ); } /** * Adds slashes to a string or recursively adds slashes to strings within an array. * * This should be used when preparing data for core API that expects slashed data. * This should not be used to escape data going directly into an SQL query. * * @since 3.6.0 * @since 5.5.0 Non-string values are left untouched. * * @param string|array $value String or array of data to slash. * @return string|array Slashed `$value`, in the same type as supplied. */ function wp_slash( $value ) { if ( is_array( $value ) ) { $value = array_map( 'wp_slash', $value ); } if ( is_string( $value ) ) { return addslashes( $value ); } return $value; } /** * Removes slashes from a string or recursively removes slashes from strings within an array. * * This should be used to remove slashes from data passed to core API that * expects data to be unslashed. * * @since 3.6.0 * * @param string|array $value String or array of data to unslash. * @return string|array Unslashed `$value`, in the same type as supplied. */ function wp_unslash( $value ) { return stripslashes_deep( $value ); } /** * Extracts and returns the first URL from passed content. * * @since 3.6.0 * * @param string $content A string which might contain a URL. * @return string|false The found URL. */ function get_url_in_content( $content ) { if ( empty( $content ) ) { return false; } if ( preg_match( '/]*?href=([\'"])(.+?)\1/is', $content, $matches ) ) { return sanitize_url( $matches[2] ); } return false; } /** * Returns the regexp for common whitespace characters. * * By default, spaces include new lines, tabs, nbsp entities, and the UTF-8 nbsp. * This is designed to replace the PCRE \s sequence. In ticket #22692, that * sequence was found to be unreliable due to random inclusion of the A0 byte. * * @since 4.0.0 * * @return string The spaces regexp. */ function wp_spaces_regexp() { static $spaces = ''; if ( empty( $spaces ) ) { /** * Filters the regexp for common whitespace characters. * * This string is substituted for the \s sequence as needed in regular * expressions. For websites not written in English, different characters * may represent whitespace. For websites not encoded in UTF-8, the 0xC2 0xA0 * sequence may not be in use. * * @since 4.0.0 * * @param string $spaces Regexp pattern for matching common whitespace characters. */ $spaces = apply_filters( 'wp_spaces_regexp', '[\r\n\t ]|\xC2\xA0| ' ); } return $spaces; } /** * Enqueues the important emoji-related styles. * * @since 6.4.0 */ function wp_enqueue_emoji_styles() { // Back-compat for plugins that disable functionality by unhooking this action. $action = is_admin() ? 'admin_print_styles' : 'wp_print_styles'; if ( ! has_action( $action, 'print_emoji_styles' ) ) { return; } remove_action( $action, 'print_emoji_styles' ); $emoji_styles = ' img.wp-smiley, img.emoji { display: inline !important; border: none !important; box-shadow: none !important; height: 1em !important; width: 1em !important; margin: 0 0.07em !important; vertical-align: -0.1em !important; background: none !important; padding: 0 !important; }'; $handle = 'wp-emoji-styles'; wp_register_style( $handle, false ); wp_add_inline_style( $handle, $emoji_styles ); wp_enqueue_style( $handle ); } /** * Prints the inline Emoji detection script if it is not already printed. * * @since 4.2.0 */ function print_emoji_detection_script() { static $printed = false; if ( $printed ) { return; } $printed = true; _print_emoji_detection_script(); } /** * Prints inline Emoji detection script. * * @ignore * @since 4.6.0 * @access private */ function _print_emoji_detection_script() { $settings = array( /** * Filters the URL where emoji png images are hosted. * * @since 4.2.0 * * @param string $url The emoji base URL for png images. */ 'baseUrl' => apply_filters( 'emoji_url', 'https://s.w.org/images/core/emoji/15.0.3/72x72/' ), /** * Filters the extension of the emoji png files. * * @since 4.2.0 * * @param string $extension The emoji extension for png files. Default .png. */ 'ext' => apply_filters( 'emoji_ext', '.png' ), /** * Filters the URL where emoji SVG images are hosted. * * @since 4.6.0 * * @param string $url The emoji base URL for svg images. */ 'svgUrl' => apply_filters( 'emoji_svg_url', 'https://s.w.org/images/core/emoji/15.0.3/svg/' ), /** * Filters the extension of the emoji SVG files. * * @since 4.6.0 * * @param string $extension The emoji extension for svg files. Default .svg. */ 'svgExt' => apply_filters( 'emoji_svg_ext', '.svg' ), ); $version = 'ver=' . get_bloginfo( 'version' ); if ( SCRIPT_DEBUG ) { $settings['source'] = array( /** This filter is documented in wp-includes/class-wp-scripts.php */ 'wpemoji' => apply_filters( 'script_loader_src', includes_url( "js/wp-emoji.js?$version" ), 'wpemoji' ), /** This filter is documented in wp-includes/class-wp-scripts.php */ 'twemoji' => apply_filters( 'script_loader_src', includes_url( "js/twemoji.js?$version" ), 'twemoji' ), ); } else { $settings['source'] = array( /** This filter is documented in wp-includes/class-wp-scripts.php */ 'concatemoji' => apply_filters( 'script_loader_src', includes_url( "js/wp-emoji-release.min.js?$version" ), 'concatemoji' ), ); } wp_print_inline_script_tag( sprintf( 'window._wpemojiSettings = %s;', wp_json_encode( $settings ) ) . "\n" . file_get_contents( ABSPATH . WPINC . '/js/wp-emoji-loader' . wp_scripts_get_suffix() . '.js' ) ); } /** * Converts emoji characters to their equivalent HTML entity. * * This allows us to store emoji in a DB using the utf8 character set. * * @since 4.2.0 * * @param string $content The content to encode. * @return string The encoded content. */ function wp_encode_emoji( $content ) { $emoji = _wp_emoji_list( 'partials' ); foreach ( $emoji as $emojum ) { $emoji_char = html_entity_decode( $emojum ); if ( str_contains( $content, $emoji_char ) ) { $content = preg_replace( "/$emoji_char/", $emojum, $content ); } } return $content; } /** * Converts emoji to a static img element. * * @since 4.2.0 * * @param string $text The content to encode. * @return string The encoded content. */ function wp_staticize_emoji( $text ) { if ( ! str_contains( $text, '&#x' ) ) { if ( ( function_exists( 'mb_check_encoding' ) && mb_check_encoding( $text, 'ASCII' ) ) || ! preg_match( '/[^\x00-\x7F]/', $text ) ) { // The text doesn't contain anything that might be emoji, so we can return early. return $text; } else { $encoded_text = wp_encode_emoji( $text ); if ( $encoded_text === $text ) { return $encoded_text; } $text = $encoded_text; } } $emoji = _wp_emoji_list( 'entities' ); // Quickly narrow down the list of emoji that might be in the text and need replacing. $possible_emoji = array(); foreach ( $emoji as $emojum ) { if ( str_contains( $text, $emojum ) ) { $possible_emoji[ $emojum ] = html_entity_decode( $emojum ); } } if ( ! $possible_emoji ) { return $text; } /** This filter is documented in wp-includes/formatting.php */ $cdn_url = apply_filters( 'emoji_url', 'https://s.w.org/images/core/emoji/15.0.3/72x72/' ); /** This filter is documented in wp-includes/formatting.php */ $ext = apply_filters( 'emoji_ext', '.png' ); $output = ''; /* * HTML loop taken from smiley function, which was taken from texturize function. * It'll never be consolidated. * * First, capture the tags as well as in between. */ $textarr = preg_split( '/(<.*>)/U', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); $stop = count( $textarr ); // Ignore processing of specific tags. $tags_to_ignore = 'code|pre|style|script|textarea'; $ignore_block_element = ''; for ( $i = 0; $i < $stop; $i++ ) { $content = $textarr[ $i ]; // If we're in an ignore block, wait until we find its closing tag. if ( '' === $ignore_block_element && preg_match( '/^<(' . $tags_to_ignore . ')>/', $content, $matches ) ) { $ignore_block_element = $matches[1]; } // If it's not a tag and not in ignore block. if ( '' === $ignore_block_element && strlen( $content ) > 0 && '<' !== $content[0] && str_contains( $content, '&#x' ) ) { foreach ( $possible_emoji as $emojum => $emoji_char ) { if ( ! str_contains( $content, $emojum ) ) { continue; } $file = str_replace( ';&#x', '-', $emojum ); $file = str_replace( array( '&#x', ';' ), '', $file ); $entity = sprintf( '%s', $cdn_url . $file . $ext, $emoji_char ); $content = str_replace( $emojum, $entity, $content ); } } // Did we exit ignore block? if ( '' !== $ignore_block_element && '' === $content ) { $ignore_block_element = ''; } $output .= $content; } // Finally, remove any stray U+FE0F characters. $output = str_replace( '️', '', $output ); return $output; } /** * Converts emoji in emails into static images. * * @since 4.2.0 * * @param array $mail The email data array. * @return array The email data array, with emoji in the message staticized. */ function wp_staticize_emoji_for_email( $mail ) { if ( ! isset( $mail['message'] ) ) { return $mail; } /* * We can only transform the emoji into images if it's a `text/html` email. * To do that, here's a cut down version of the same process that happens * in wp_mail() - get the `Content-Type` from the headers, if there is one, * then pass it through the {@see 'wp_mail_content_type'} filter, in case * a plugin is handling changing the `Content-Type`. */ $headers = array(); if ( isset( $mail['headers'] ) ) { if ( is_array( $mail['headers'] ) ) { $headers = $mail['headers']; } else { $headers = explode( "\n", str_replace( "\r\n", "\n", $mail['headers'] ) ); } } foreach ( $headers as $header ) { if ( ! str_contains( $header, ':' ) ) { continue; } // Explode them out. list( $name, $content ) = explode( ':', trim( $header ), 2 ); // Cleanup crew. $name = trim( $name ); $content = trim( $content ); if ( 'content-type' === strtolower( $name ) ) { if ( str_contains( $content, ';' ) ) { list( $type, $charset ) = explode( ';', $content ); $content_type = trim( $type ); } else { $content_type = trim( $content ); } break; } } // Set Content-Type if we don't have a content-type from the input headers. if ( ! isset( $content_type ) ) { $content_type = 'text/plain'; } /** This filter is documented in wp-includes/pluggable.php */ $content_type = apply_filters( 'wp_mail_content_type', $content_type ); if ( 'text/html' === $content_type ) { $mail['message'] = wp_staticize_emoji( $mail['message'] ); } return $mail; } /** * Returns arrays of emoji data. * * These arrays are automatically built from the regex in twemoji.js - if they need to be updated, * you should update the regex there, then run the `npm run grunt precommit:emoji` job. * * @since 4.9.0 * @access private * * @param string $type Optional. Which array type to return. Accepts 'partials' or 'entities', default 'entities'. * @return array An array to match all emoji that WordPress recognises. */ function _wp_emoji_list( $type = 'entities' ) { // Do not remove the START/END comments - they're used to find where to insert the arrays. // START: emoji arrays $entities = array( '👨🏻‍❤️‍💋‍👨🏻', '👨🏻‍❤️‍💋‍👨🏼', '👨🏻‍❤️‍💋‍👨🏽', '👨🏻‍❤️‍💋‍👨🏾', '👨🏻‍❤️‍💋‍👨🏿', '👨🏼‍❤️‍💋‍👨🏻', '👨🏼‍❤️‍💋‍👨🏼', '👨🏼‍❤️‍💋‍👨🏽', '👨🏼‍❤️‍💋‍👨🏾', '👨🏼‍❤️‍💋‍👨🏿', '👨🏽‍❤️‍💋‍👨🏻', '👨🏽‍❤️‍💋‍👨🏼', '👨🏽‍❤️‍💋‍👨🏽', '👨🏽‍❤️‍💋‍👨🏾', '👨🏽‍❤️‍💋‍👨🏿', '👨🏾‍❤️‍💋‍👨🏻', '👨🏾‍❤️‍💋‍👨🏼', '👨🏾‍❤️‍💋‍👨🏽', '👨🏾‍❤️‍💋‍👨🏾', '👨🏾‍❤️‍💋‍👨🏿', '👨🏿‍❤️‍💋‍👨🏻', '👨🏿‍❤️‍💋‍👨🏼', '👨🏿‍❤️‍💋‍👨🏽', '👨🏿‍❤️‍💋‍👨🏾', '👨🏿‍❤️‍💋‍👨🏿', '👩🏻‍❤️‍💋‍👨🏻', '👩🏻‍❤️‍💋‍👨🏼', '👩🏻‍❤️‍💋‍👨🏽', '👩🏻‍❤️‍💋‍👨🏾', '👩🏻‍❤️‍💋‍👨🏿', '👩🏻‍❤️‍💋‍👩🏻', '👩🏻‍❤️‍💋‍👩🏼', '👩🏻‍❤️‍💋‍👩🏽', '👩🏻‍❤️‍💋‍👩🏾', '👩🏻‍❤️‍💋‍👩🏿', '👩🏼‍❤️‍💋‍👨🏻', '👩🏼‍❤️‍💋‍👨🏼', '👩🏼‍❤️‍💋‍👨🏽', '👩🏼‍❤️‍💋‍👨🏾', '👩🏼‍❤️‍💋‍👨🏿', '👩🏼‍❤️‍💋‍👩🏻', '👩🏼‍❤️‍💋‍👩🏼', '👩🏼‍❤️‍💋‍👩🏽', '👩🏼‍❤️‍💋‍👩🏾', '👩🏼‍❤️‍💋‍👩🏿', '👩🏽‍❤️‍💋‍👨🏻', '👩🏽‍❤️‍💋‍👨🏼', '👩🏽‍❤️‍💋‍👨🏽', '👩🏽‍❤️‍💋‍👨🏾', '👩🏽‍❤️‍💋‍👨🏿', '👩🏽‍❤️‍💋‍👩🏻', '👩🏽‍❤️‍💋‍👩🏼', '👩🏽‍❤️‍💋‍👩🏽', '👩🏽‍❤️‍💋‍👩🏾', '👩🏽‍❤️‍💋‍👩🏿', '👩🏾‍❤️‍💋‍👨🏻', '👩🏾‍❤️‍💋‍👨🏼', '👩🏾‍❤️‍💋‍👨🏽', '👩🏾‍❤️‍💋‍👨🏾', '👩🏾‍❤️‍💋‍👨🏿', '👩🏾‍❤️‍💋‍👩🏻', '👩🏾‍❤️‍💋‍👩🏼', '👩🏾‍❤️‍💋‍👩🏽', '👩🏾‍❤️‍💋‍👩🏾', '👩🏾‍❤️‍💋‍👩🏿', '👩🏿‍❤️‍💋‍👨🏻', '👩🏿‍❤️‍💋‍👨🏼', '👩🏿‍❤️‍💋‍👨🏽', '👩🏿‍❤️‍💋‍👨🏾', '👩🏿‍❤️‍💋‍👨🏿', '👩🏿‍❤️‍💋‍👩🏻', '👩🏿‍❤️‍💋‍👩🏼', '👩🏿‍❤️‍💋‍👩🏽', '👩🏿‍❤️‍💋‍👩🏾', '👩🏿‍❤️‍💋‍👩🏿', '🧑🏻‍❤️‍💋‍🧑🏼', '🧑🏻‍❤️‍💋‍🧑🏽', '🧑🏻‍❤️‍💋‍🧑🏾', '🧑🏻‍❤️‍💋‍🧑🏿', '🧑🏼‍❤️‍💋‍🧑🏻', '🧑🏼‍❤️‍💋‍🧑🏽', '🧑🏼‍❤️‍💋‍🧑🏾', '🧑🏼‍❤️‍💋‍🧑🏿', '🧑🏽‍❤️‍💋‍🧑🏻', '🧑🏽‍❤️‍💋‍🧑🏼', '🧑🏽‍❤️‍💋‍🧑🏾', '🧑🏽‍❤️‍💋‍🧑🏿', '🧑🏾‍❤️‍💋‍🧑🏻', '🧑🏾‍❤️‍💋‍🧑🏼', '🧑🏾‍❤️‍💋‍🧑🏽', '🧑🏾‍❤️‍💋‍🧑🏿', '🧑🏿‍❤️‍💋‍🧑🏻', '🧑🏿‍❤️‍💋‍🧑🏼', '🧑🏿‍❤️‍💋‍🧑🏽', '🧑🏿‍❤️‍💋‍🧑🏾', '👨🏻‍❤️‍👨🏻', '👨🏻‍❤️‍👨🏼', '👨🏻‍❤️‍👨🏽', '👨🏻‍❤️‍👨🏾', '👨🏻‍❤️‍👨🏿', '👨🏼‍❤️‍👨🏻', '👨🏼‍❤️‍👨🏼', '👨🏼‍❤️‍👨🏽', '👨🏼‍❤️‍👨🏾', '👨🏼‍❤️‍👨🏿', '👨🏽‍❤️‍👨🏻', '👨🏽‍❤️‍👨🏼', '👨🏽‍❤️‍👨🏽', '👨🏽‍❤️‍👨🏾', '👨🏽‍❤️‍👨🏿', '👨🏾‍❤️‍👨🏻', '👨🏾‍❤️‍👨🏼', '👨🏾‍❤️‍👨🏽', '👨🏾‍❤️‍👨🏾', '👨🏾‍❤️‍👨🏿', '👨🏿‍❤️‍👨🏻', '👨🏿‍❤️‍👨🏼', '👨🏿‍❤️‍👨🏽', '👨🏿‍❤️‍👨🏾', '👨🏿‍❤️‍👨🏿', '👩🏻‍❤️‍👨🏻', '👩🏻‍❤️‍👨🏼', '👩🏻‍❤️‍👨🏽', '👩🏻‍❤️‍👨🏾', '👩🏻‍❤️‍👨🏿', '👩🏻‍❤️‍👩🏻', '👩🏻‍❤️‍👩🏼', '👩🏻‍❤️‍👩🏽', '👩🏻‍❤️‍👩🏾', '👩🏻‍❤️‍👩🏿', '👩🏼‍❤️‍👨🏻', '👩🏼‍❤️‍👨🏼', '👩🏼‍❤️‍👨🏽', '👩🏼‍❤️‍👨🏾', '👩🏼‍❤️‍👨🏿', '👩🏼‍❤️‍👩🏻', '👩🏼‍❤️‍👩🏼', '👩🏼‍❤️‍👩🏽', '👩🏼‍❤️‍👩🏾', '👩🏼‍❤️‍👩🏿', '👩🏽‍❤️‍👨🏻', '👩🏽‍❤️‍👨🏼', '👩🏽‍❤️‍👨🏽', '👩🏽‍❤️‍👨🏾', '👩🏽‍❤️‍👨🏿', '👩🏽‍❤️‍👩🏻', '👩🏽‍❤️‍👩🏼', '👩🏽‍❤️‍👩🏽', '👩🏽‍❤️‍👩🏾', '👩🏽‍❤️‍👩🏿', '👩🏾‍❤️‍👨🏻', '👩🏾‍❤️‍👨🏼', '👩🏾‍❤️‍👨🏽', '👩🏾‍❤️‍👨🏾', '👩🏾‍❤️‍👨🏿', '👩🏾‍❤️‍👩🏻', '👩🏾‍❤️‍👩🏼', '👩🏾‍❤️‍👩🏽', '👩🏾‍❤️‍👩🏾', '👩🏾‍❤️‍👩🏿', '👩🏿‍❤️‍👨🏻', '👩🏿‍❤️‍👨🏼', '👩🏿‍❤️‍👨🏽', '👩🏿‍❤️‍👨🏾', '👩🏿‍❤️‍👨🏿', '👩🏿‍❤️‍👩🏻', '👩🏿‍❤️‍👩🏼', '👩🏿‍❤️‍👩🏽', '👩🏿‍❤️‍👩🏾', '👩🏿‍❤️‍👩🏿', '🧑🏻‍❤️‍🧑🏼', '🧑🏻‍❤️‍🧑🏽', '🧑🏻‍❤️‍🧑🏾', '🧑🏻‍❤️‍🧑🏿', '🧑🏼‍❤️‍🧑🏻', '🧑🏼‍❤️‍🧑🏽', '🧑🏼‍❤️‍🧑🏾', '🧑🏼‍❤️‍🧑🏿', '🧑🏽‍❤️‍🧑🏻', '🧑🏽‍❤️‍🧑🏼', '🧑🏽‍❤️‍🧑🏾', '🧑🏽‍❤️‍🧑🏿', '🧑🏾‍❤️‍🧑🏻', '🧑🏾‍❤️‍🧑🏼', '🧑🏾‍❤️‍🧑🏽', '🧑🏾‍❤️‍🧑🏿', '🧑🏿‍❤️‍🧑🏻', '🧑🏿‍❤️‍🧑🏼', '🧑🏿‍❤️‍🧑🏽', '🧑🏿‍❤️‍🧑🏾', '👨‍❤️‍💋‍👨', '👩‍❤️‍💋‍👨', '👩‍❤️‍💋‍👩', '🏴󠁧󠁢󠁥󠁮󠁧󠁿', '🏴󠁧󠁢󠁳󠁣󠁴󠁿', '🏴󠁧󠁢󠁷󠁬󠁳󠁿', '👨🏻‍🤝‍👨🏼', '👨🏻‍🤝‍👨🏽', '👨🏻‍🤝‍👨🏾', '👨🏻‍🤝‍👨🏿', '👨🏼‍🤝‍👨🏻', '👨🏼‍🤝‍👨🏽', '👨🏼‍🤝‍👨🏾', '👨🏼‍🤝‍👨🏿', '👨🏽‍🤝‍👨🏻', '👨🏽‍🤝‍👨🏼', '👨🏽‍🤝‍👨🏾', '👨🏽‍🤝‍👨🏿', '👨🏾‍🤝‍👨🏻', '👨🏾‍🤝‍👨🏼', '👨🏾‍🤝‍👨🏽', '👨🏾‍🤝‍👨🏿', '👨🏿‍🤝‍👨🏻', '👨🏿‍🤝‍👨🏼', '👨🏿‍🤝‍👨🏽', '👨🏿‍🤝‍👨🏾', '👩🏻‍🤝‍👨🏼', '👩🏻‍🤝‍👨🏽', '👩🏻‍🤝‍👨🏾', '👩🏻‍🤝‍👨🏿', '👩🏻‍🤝‍👩🏼', '👩🏻‍🤝‍👩🏽', '👩🏻‍🤝‍👩🏾', '👩🏻‍🤝‍👩🏿', '👩🏼‍🤝‍👨🏻', '👩🏼‍🤝‍👨🏽', '👩🏼‍🤝‍👨🏾', '👩🏼‍🤝‍👨🏿', '👩🏼‍🤝‍👩🏻', '👩🏼‍🤝‍👩🏽', '👩🏼‍🤝‍👩🏾', '👩🏼‍🤝‍👩🏿', '👩🏽‍🤝‍👨🏻', '👩🏽‍🤝‍👨🏼', '👩🏽‍🤝‍👨🏾', '👩🏽‍🤝‍👨🏿', '👩🏽‍🤝‍👩🏻', '👩🏽‍🤝‍👩🏼', '👩🏽‍🤝‍👩🏾', '👩🏽‍🤝‍👩🏿', '👩🏾‍🤝‍👨🏻', '👩🏾‍🤝‍👨🏼', '👩🏾‍🤝‍👨🏽', '👩🏾‍🤝‍👨🏿', '👩🏾‍🤝‍👩🏻', '👩🏾‍🤝‍👩🏼', '👩🏾‍🤝‍👩🏽', '👩🏾‍🤝‍👩🏿', '👩🏿‍🤝‍👨🏻', '👩🏿‍🤝‍👨🏼', '👩🏿‍🤝‍👨🏽', '👩🏿‍🤝‍👨🏾', '👩🏿‍🤝‍👩🏻', '👩🏿‍🤝‍👩🏼', '👩🏿‍🤝‍👩🏽', '👩🏿‍🤝‍👩🏾', '🧑🏻‍🤝‍🧑🏻', '🧑🏻‍🤝‍🧑🏼', '🧑🏻‍🤝‍🧑🏽', '🧑🏻‍🤝‍🧑🏾', '🧑🏻‍🤝‍🧑🏿', '🧑🏼‍🤝‍🧑🏻', '🧑🏼‍🤝‍🧑🏼', '🧑🏼‍🤝‍🧑🏽', '🧑🏼‍🤝‍🧑🏾', '🧑🏼‍🤝‍🧑🏿', '🧑🏽‍🤝‍🧑🏻', '🧑🏽‍🤝‍🧑🏼', '🧑🏽‍🤝‍🧑🏽', '🧑🏽‍🤝‍🧑🏾', '🧑🏽‍🤝‍🧑🏿', '🧑🏾‍🤝‍🧑🏻', '🧑🏾‍🤝‍🧑🏼', '🧑🏾‍🤝‍🧑🏽', '🧑🏾‍🤝‍🧑🏾', '🧑🏾‍🤝‍🧑🏿', '🧑🏿‍🤝‍🧑🏻', '🧑🏿‍🤝‍🧑🏼', '🧑🏿‍🤝‍🧑🏽', '🧑🏿‍🤝‍🧑🏾', '🧑🏿‍🤝‍🧑🏿', '👨‍👨‍👦‍👦', '👨‍👨‍👧‍👦', '👨‍👨‍👧‍👧', '👨‍👩‍👦‍👦', '👨‍👩‍👧‍👦', '👨‍👩‍👧‍👧', '👩‍👩‍👦‍👦', '👩‍👩‍👧‍👦', '👩‍👩‍👧‍👧', '👨‍❤️‍👨', '👩‍❤️‍👨', '👩‍❤️‍👩', '🫱🏻‍🫲🏼', '🫱🏻‍🫲🏽', '🫱🏻‍🫲🏾', '🫱🏻‍🫲🏿', '🫱🏼‍🫲🏻', '🫱🏼‍🫲🏽', '🫱🏼‍🫲🏾', '🫱🏼‍🫲🏿', '🫱🏽‍🫲🏻', '🫱🏽‍🫲🏼', '🫱🏽‍🫲🏾', '🫱🏽‍🫲🏿', '🫱🏾‍🫲🏻', '🫱🏾‍🫲🏼', '🫱🏾‍🫲🏽', '🫱🏾‍🫲🏿', '🫱🏿‍🫲🏻', '🫱🏿‍🫲🏼', '🫱🏿‍🫲🏽', '🫱🏿‍🫲🏾', '👨‍👦‍👦', '👨‍👧‍👦', '👨‍👧‍👧', '👨‍👨‍👦', '👨‍👨‍👧', '👨‍👩‍👦', '👨‍👩‍👧', '👩‍👦‍👦', '👩‍👧‍👦', '👩‍👧‍👧', '👩‍👩‍👦', '👩‍👩‍👧', '🧑‍🤝‍🧑', '🏃🏻‍♀️', '🏃🏻‍♂️', '🏃🏼‍♀️', '🏃🏼‍♂️', '🏃🏽‍♀️', '🏃🏽‍♂️', '🏃🏾‍♀️', '🏃🏾‍♂️', '🏃🏿‍♀️', '🏃🏿‍♂️', '🏄🏻‍♀️', '🏄🏻‍♂️', '🏄🏼‍♀️', '🏄🏼‍♂️', '🏄🏽‍♀️', '🏄🏽‍♂️', '🏄🏾‍♀️', '🏄🏾‍♂️', '🏄🏿‍♀️', '🏄🏿‍♂️', '🏊🏻‍♀️', '🏊🏻‍♂️', '🏊🏼‍♀️', '🏊🏼‍♂️', '🏊🏽‍♀️', '🏊🏽‍♂️', '🏊🏾‍♀️', '🏊🏾‍♂️', '🏊🏿‍♀️', '🏊🏿‍♂️', '🏋🏻‍♀️', '🏋🏻‍♂️', '🏋🏼‍♀️', '🏋🏼‍♂️', '🏋🏽‍♀️', '🏋🏽‍♂️', '🏋🏾‍♀️', '🏋🏾‍♂️', '🏋🏿‍♀️', '🏋🏿‍♂️', '🏌🏻‍♀️', '🏌🏻‍♂️', '🏌🏼‍♀️', '🏌🏼‍♂️', '🏌🏽‍♀️', '🏌🏽‍♂️', '🏌🏾‍♀️', '🏌🏾‍♂️', '🏌🏿‍♀️', '🏌🏿‍♂️', '👨🏻‍⚕️', '👨🏻‍⚖️', '👨🏻‍✈️', '👨🏼‍⚕️', '👨🏼‍⚖️', '👨🏼‍✈️', '👨🏽‍⚕️', '👨🏽‍⚖️', '👨🏽‍✈️', '👨🏾‍⚕️', '👨🏾‍⚖️', '👨🏾‍✈️', '👨🏿‍⚕️', '👨🏿‍⚖️', '👨🏿‍✈️', '👩🏻‍⚕️', '👩🏻‍⚖️', '👩🏻‍✈️', '👩🏼‍⚕️', '👩🏼‍⚖️', '👩🏼‍✈️', '👩🏽‍⚕️', '👩🏽‍⚖️', '👩🏽‍✈️', '👩🏾‍⚕️', '👩🏾‍⚖️', '👩🏾‍✈️', '👩🏿‍⚕️', '👩🏿‍⚖️', '👩🏿‍✈️', '👮🏻‍♀️', '👮🏻‍♂️', '👮🏼‍♀️', '👮🏼‍♂️', '👮🏽‍♀️', '👮🏽‍♂️', '👮🏾‍♀️', '👮🏾‍♂️', '👮🏿‍♀️', '👮🏿‍♂️', '👰🏻‍♀️', '👰🏻‍♂️', '👰🏼‍♀️', '👰🏼‍♂️', '👰🏽‍♀️', '👰🏽‍♂️', '👰🏾‍♀️', '👰🏾‍♂️', '👰🏿‍♀️', '👰🏿‍♂️', '👱🏻‍♀️', '👱🏻‍♂️', '👱🏼‍♀️', '👱🏼‍♂️', '👱🏽‍♀️', '👱🏽‍♂️', '👱🏾‍♀️', '👱🏾‍♂️', '👱🏿‍♀️', '👱🏿‍♂️', '👳🏻‍♀️', '👳🏻‍♂️', '👳🏼‍♀️', '👳🏼‍♂️', '👳🏽‍♀️', '👳🏽‍♂️', '👳🏾‍♀️', '👳🏾‍♂️', '👳🏿‍♀️', '👳🏿‍♂️', '👷🏻‍♀️', '👷🏻‍♂️', '👷🏼‍♀️', '👷🏼‍♂️', '👷🏽‍♀️', '👷🏽‍♂️', '👷🏾‍♀️', '👷🏾‍♂️', '👷🏿‍♀️', '👷🏿‍♂️', '💁🏻‍♀️', '💁🏻‍♂️', '💁🏼‍♀️', '💁🏼‍♂️', '💁🏽‍♀️', '💁🏽‍♂️', '💁🏾‍♀️', '💁🏾‍♂️', '💁🏿‍♀️', '💁🏿‍♂️', '💂🏻‍♀️', '💂🏻‍♂️', '💂🏼‍♀️', '💂🏼‍♂️', '💂🏽‍♀️', '💂🏽‍♂️', '💂🏾‍♀️', '💂🏾‍♂️', '💂🏿‍♀️', '💂🏿‍♂️', '💆🏻‍♀️', '💆🏻‍♂️', '💆🏼‍♀️', '💆🏼‍♂️', '💆🏽‍♀️', '💆🏽‍♂️', '💆🏾‍♀️', '💆🏾‍♂️', '💆🏿‍♀️', '💆🏿‍♂️', '💇🏻‍♀️', '💇🏻‍♂️', '💇🏼‍♀️', '💇🏼‍♂️', '💇🏽‍♀️', '💇🏽‍♂️', '💇🏾‍♀️', '💇🏾‍♂️', '💇🏿‍♀️', '💇🏿‍♂️', '🕴🏻‍♀️', '🕴🏻‍♂️', '🕴🏼‍♀️', '🕴🏼‍♂️', '🕴🏽‍♀️', '🕴🏽‍♂️', '🕴🏾‍♀️', '🕴🏾‍♂️', '🕴🏿‍♀️', '🕴🏿‍♂️', '🕵🏻‍♀️', '🕵🏻‍♂️', '🕵🏼‍♀️', '🕵🏼‍♂️', '🕵🏽‍♀️', '🕵🏽‍♂️', '🕵🏾‍♀️', '🕵🏾‍♂️', '🕵🏿‍♀️', '🕵🏿‍♂️', '🙅🏻‍♀️', '🙅🏻‍♂️', '🙅🏼‍♀️', '🙅🏼‍♂️', '🙅🏽‍♀️', '🙅🏽‍♂️', '🙅🏾‍♀️', '🙅🏾‍♂️', '🙅🏿‍♀️', '🙅🏿‍♂️', '🙆🏻‍♀️', '🙆🏻‍♂️', '🙆🏼‍♀️', '🙆🏼‍♂️', '🙆🏽‍♀️', '🙆🏽‍♂️', '🙆🏾‍♀️', '🙆🏾‍♂️', '🙆🏿‍♀️', '🙆🏿‍♂️', '🙇🏻‍♀️', '🙇🏻‍♂️', '🙇🏼‍♀️', '🙇🏼‍♂️', '🙇🏽‍♀️', '🙇🏽‍♂️', '🙇🏾‍♀️', '🙇🏾‍♂️', '🙇🏿‍♀️', '🙇🏿‍♂️', '🙋🏻‍♀️', '🙋🏻‍♂️', '🙋🏼‍♀️', '🙋🏼‍♂️', '🙋🏽‍♀️', '🙋🏽‍♂️', '🙋🏾‍♀️', '🙋🏾‍♂️', '🙋🏿‍♀️', '🙋🏿‍♂️', '🙍🏻‍♀️', '🙍🏻‍♂️', '🙍🏼‍♀️', '🙍🏼‍♂️', '🙍🏽‍♀️', '🙍🏽‍♂️', '🙍🏾‍♀️', '🙍🏾‍♂️', '🙍🏿‍♀️', '🙍🏿‍♂️', '🙎🏻‍♀️', '🙎🏻‍♂️', '🙎🏼‍♀️', '🙎🏼‍♂️', '🙎🏽‍♀️', '🙎🏽‍♂️', '🙎🏾‍♀️', '🙎🏾‍♂️', '🙎🏿‍♀️', '🙎🏿‍♂️', '🚣🏻‍♀️', '🚣🏻‍♂️', '🚣🏼‍♀️', '🚣🏼‍♂️', '🚣🏽‍♀️', '🚣🏽‍♂️', '🚣🏾‍♀️', '🚣🏾‍♂️', '🚣🏿‍♀️', '🚣🏿‍♂️', '🚴🏻‍♀️', '🚴🏻‍♂️', '🚴🏼‍♀️', '🚴🏼‍♂️', '🚴🏽‍♀️', '🚴🏽‍♂️', '🚴🏾‍♀️', '🚴🏾‍♂️', '🚴🏿‍♀️', '🚴🏿‍♂️', '🚵🏻‍♀️', '🚵🏻‍♂️', '🚵🏼‍♀️', '🚵🏼‍♂️', '🚵🏽‍♀️', '🚵🏽‍♂️', '🚵🏾‍♀️', '🚵🏾‍♂️', '🚵🏿‍♀️', '🚵🏿‍♂️', '🚶🏻‍♀️', '🚶🏻‍♂️', '🚶🏼‍♀️', '🚶🏼‍♂️', '🚶🏽‍♀️', '🚶🏽‍♂️', '🚶🏾‍♀️', '🚶🏾‍♂️', '🚶🏿‍♀️', '🚶🏿‍♂️', '🤦🏻‍♀️', '🤦🏻‍♂️', '🤦🏼‍♀️', '🤦🏼‍♂️', '🤦🏽‍♀️', '🤦🏽‍♂️', '🤦🏾‍♀️', '🤦🏾‍♂️', '🤦🏿‍♀️', '🤦🏿‍♂️', '🤵🏻‍♀️', '🤵🏻‍♂️', '🤵🏼‍♀️', '🤵🏼‍♂️', '🤵🏽‍♀️', '🤵🏽‍♂️', '🤵🏾‍♀️', '🤵🏾‍♂️', '🤵🏿‍♀️', '🤵🏿‍♂️', '🤷🏻‍♀️', '🤷🏻‍♂️', '🤷🏼‍♀️', '🤷🏼‍♂️', '🤷🏽‍♀️', '🤷🏽‍♂️', '🤷🏾‍♀️', '🤷🏾‍♂️', '🤷🏿‍♀️', '🤷🏿‍♂️', '🤸🏻‍♀️', '🤸🏻‍♂️', '🤸🏼‍♀️', '🤸🏼‍♂️', '🤸🏽‍♀️', '🤸🏽‍♂️', '🤸🏾‍♀️', '🤸🏾‍♂️', '🤸🏿‍♀️', '🤸🏿‍♂️', '🤹🏻‍♀️', '🤹🏻‍♂️', '🤹🏼‍♀️', '🤹🏼‍♂️', '🤹🏽‍♀️', '🤹🏽‍♂️', '🤹🏾‍♀️', '🤹🏾‍♂️', '🤹🏿‍♀️', '🤹🏿‍♂️', '🤽🏻‍♀️', '🤽🏻‍♂️', '🤽🏼‍♀️', '🤽🏼‍♂️', '🤽🏽‍♀️', '🤽🏽‍♂️', '🤽🏾‍♀️', '🤽🏾‍♂️', '🤽🏿‍♀️', '🤽🏿‍♂️', '🤾🏻‍♀️', '🤾🏻‍♂️', '🤾🏼‍♀️', '🤾🏼‍♂️', '🤾🏽‍♀️', '🤾🏽‍♂️', '🤾🏾‍♀️', '🤾🏾‍♂️', '🤾🏿‍♀️', '🤾🏿‍♂️', '🦸🏻‍♀️', '🦸🏻‍♂️', '🦸🏼‍♀️', '🦸🏼‍♂️', '🦸🏽‍♀️', '🦸🏽‍♂️', '🦸🏾‍♀️', '🦸🏾‍♂️', '🦸🏿‍♀️', '🦸🏿‍♂️', '🦹🏻‍♀️', '🦹🏻‍♂️', '🦹🏼‍♀️', '🦹🏼‍♂️', '🦹🏽‍♀️', '🦹🏽‍♂️', '🦹🏾‍♀️', '🦹🏾‍♂️', '🦹🏿‍♀️', '🦹🏿‍♂️', '🧍🏻‍♀️', '🧍🏻‍♂️', '🧍🏼‍♀️', '🧍🏼‍♂️', '🧍🏽‍♀️', '🧍🏽‍♂️', '🧍🏾‍♀️', '🧍🏾‍♂️', '🧍🏿‍♀️', '🧍🏿‍♂️', '🧎🏻‍♀️', '🧎🏻‍♂️', '🧎🏼‍♀️', '🧎🏼‍♂️', '🧎🏽‍♀️', '🧎🏽‍♂️', '🧎🏾‍♀️', '🧎🏾‍♂️', '🧎🏿‍♀️', '🧎🏿‍♂️', '🧏🏻‍♀️', '🧏🏻‍♂️', '🧏🏼‍♀️', '🧏🏼‍♂️', '🧏🏽‍♀️', '🧏🏽‍♂️', '🧏🏾‍♀️', '🧏🏾‍♂️', '🧏🏿‍♀️', '🧏🏿‍♂️', '🧑🏻‍⚕️', '🧑🏻‍⚖️', '🧑🏻‍✈️', '🧑🏼‍⚕️', '🧑🏼‍⚖️', '🧑🏼‍✈️', '🧑🏽‍⚕️', '🧑🏽‍⚖️', '🧑🏽‍✈️', '🧑🏾‍⚕️', '🧑🏾‍⚖️', '🧑🏾‍✈️', '🧑🏿‍⚕️', '🧑🏿‍⚖️', '🧑🏿‍✈️', '🧔🏻‍♀️', '🧔🏻‍♂️', '🧔🏼‍♀️', '🧔🏼‍♂️', '🧔🏽‍♀️', '🧔🏽‍♂️', '🧔🏾‍♀️', '🧔🏾‍♂️', '🧔🏿‍♀️', '🧔🏿‍♂️', '🧖🏻‍♀️', '🧖🏻‍♂️', '🧖🏼‍♀️', '🧖🏼‍♂️', '🧖🏽‍♀️', '🧖🏽‍♂️', '🧖🏾‍♀️', '🧖🏾‍♂️', '🧖🏿‍♀️', '🧖🏿‍♂️', '🧗🏻‍♀️', '🧗🏻‍♂️', '🧗🏼‍♀️', '🧗🏼‍♂️', '🧗🏽‍♀️', '🧗🏽‍♂️', '🧗🏾‍♀️', '🧗🏾‍♂️', '🧗🏿‍♀️', '🧗🏿‍♂️', '🧘🏻‍♀️', '🧘🏻‍♂️', '🧘🏼‍♀️', '🧘🏼‍♂️', '🧘🏽‍♀️', '🧘🏽‍♂️', '🧘🏾‍♀️', '🧘🏾‍♂️', '🧘🏿‍♀️', '🧘🏿‍♂️', '🧙🏻‍♀️', '🧙🏻‍♂️', '🧙🏼‍♀️', '🧙🏼‍♂️', '🧙🏽‍♀️', '🧙🏽‍♂️', '🧙🏾‍♀️', '🧙🏾‍♂️', '🧙🏿‍♀️', '🧙🏿‍♂️', '🧚🏻‍♀️', '🧚🏻‍♂️', '🧚🏼‍♀️', '🧚🏼‍♂️', '🧚🏽‍♀️', '🧚🏽‍♂️', '🧚🏾‍♀️', '🧚🏾‍♂️', '🧚🏿‍♀️', '🧚🏿‍♂️', '🧛🏻‍♀️', '🧛🏻‍♂️', '🧛🏼‍♀️', '🧛🏼‍♂️', '🧛🏽‍♀️', '🧛🏽‍♂️', '🧛🏾‍♀️', '🧛🏾‍♂️', '🧛🏿‍♀️', '🧛🏿‍♂️', '🧜🏻‍♀️', '🧜🏻‍♂️', '🧜🏼‍♀️', '🧜🏼‍♂️', '🧜🏽‍♀️', '🧜🏽‍♂️', '🧜🏾‍♀️', '🧜🏾‍♂️', '🧜🏿‍♀️', '🧜🏿‍♂️', '🧝🏻‍♀️', '🧝🏻‍♂️', '🧝🏼‍♀️', '🧝🏼‍♂️', '🧝🏽‍♀️', '🧝🏽‍♂️', '🧝🏾‍♀️', '🧝🏾‍♂️', '🧝🏿‍♀️', '🧝🏿‍♂️', '🏋️‍♀️', '🏋️‍♂️', '🏌️‍♀️', '🏌️‍♂️', '🏳️‍⚧️', '🕴️‍♀️', '🕴️‍♂️', '🕵️‍♀️', '🕵️‍♂️', '⛹🏻‍♀️', '⛹🏻‍♂️', '⛹🏼‍♀️', '⛹🏼‍♂️', '⛹🏽‍♀️', '⛹🏽‍♂️', '⛹🏾‍♀️', '⛹🏾‍♂️', '⛹🏿‍♀️', '⛹🏿‍♂️', '⛹️‍♀️', '⛹️‍♂️', '👨🏻‍🌾', '👨🏻‍🍳', '👨🏻‍🍼', '👨🏻‍🎄', '👨🏻‍🎓', '👨🏻‍🎤', '👨🏻‍🎨', '👨🏻‍🏫', '👨🏻‍🏭', '👨🏻‍💻', '👨🏻‍💼', '👨🏻‍🔧', '👨🏻‍🔬', '👨🏻‍🚀', '👨🏻‍🚒', '👨🏻‍🦯', '👨🏻‍🦰', '👨🏻‍🦱', '👨🏻‍🦲', '👨🏻‍🦳', '👨🏻‍🦼', '👨🏻‍🦽', '👨🏼‍🌾', '👨🏼‍🍳', '👨🏼‍🍼', '👨🏼‍🎄', '👨🏼‍🎓', '👨🏼‍🎤', '👨🏼‍🎨', '👨🏼‍🏫', '👨🏼‍🏭', '👨🏼‍💻', '👨🏼‍💼', '👨🏼‍🔧', '👨🏼‍🔬', '👨🏼‍🚀', '👨🏼‍🚒', '👨🏼‍🦯', '👨🏼‍🦰', '👨🏼‍🦱', '👨🏼‍🦲', '👨🏼‍🦳', '👨🏼‍🦼', '👨🏼‍🦽', '👨🏽‍🌾', '👨🏽‍🍳', '👨🏽‍🍼', '👨🏽‍🎄', '👨🏽‍🎓', '👨🏽‍🎤', '👨🏽‍🎨', '👨🏽‍🏫', '👨🏽‍🏭', '👨🏽‍💻', '👨🏽‍💼', '👨🏽‍🔧', '👨🏽‍🔬', '👨🏽‍🚀', '👨🏽‍🚒', '👨🏽‍🦯', '👨🏽‍🦰', '👨🏽‍🦱', '👨🏽‍🦲', '👨🏽‍🦳', '👨🏽‍🦼', '👨🏽‍🦽', '👨🏾‍🌾', '👨🏾‍🍳', '👨🏾‍🍼', '👨🏾‍🎄', '👨🏾‍🎓', '👨🏾‍🎤', '👨🏾‍🎨', '👨🏾‍🏫', '👨🏾‍🏭', '👨🏾‍💻', '👨🏾‍💼', '👨🏾‍🔧', '👨🏾‍🔬', '👨🏾‍🚀', '👨🏾‍🚒', '👨🏾‍🦯', '👨🏾‍🦰', '👨🏾‍🦱', '👨🏾‍🦲', '👨🏾‍🦳', '👨🏾‍🦼', '👨🏾‍🦽', '👨🏿‍🌾', '👨🏿‍🍳', '👨🏿‍🍼', '👨🏿‍🎄', '👨🏿‍🎓', '👨🏿‍🎤', '👨🏿‍🎨', '👨🏿‍🏫', '👨🏿‍🏭', '👨🏿‍💻', '👨🏿‍💼', '👨🏿‍🔧', '👨🏿‍🔬', '👨🏿‍🚀', '👨🏿‍🚒', '👨🏿‍🦯', '👨🏿‍🦰', '👨🏿‍🦱', '👨🏿‍🦲', '👨🏿‍🦳', '👨🏿‍🦼', '👨🏿‍🦽', '👩🏻‍🌾', '👩🏻‍🍳', '👩🏻‍🍼', '👩🏻‍🎄', '👩🏻‍🎓', '👩🏻‍🎤', '👩🏻‍🎨', '👩🏻‍🏫', '👩🏻‍🏭', '👩🏻‍💻', '👩🏻‍💼', '👩🏻‍🔧', '👩🏻‍🔬', '👩🏻‍🚀', '👩🏻‍🚒', '👩🏻‍🦯', '👩🏻‍🦰', '👩🏻‍🦱', '👩🏻‍🦲', '👩🏻‍🦳', '👩🏻‍🦼', '👩🏻‍🦽', '👩🏼‍🌾', '👩🏼‍🍳', '👩🏼‍🍼', '👩🏼‍🎄', '👩🏼‍🎓', '👩🏼‍🎤', '👩🏼‍🎨', '👩🏼‍🏫', '👩🏼‍🏭', '👩🏼‍💻', '👩🏼‍💼', '👩🏼‍🔧', '👩🏼‍🔬', '👩🏼‍🚀', '👩🏼‍🚒', '👩🏼‍🦯', '👩🏼‍🦰', '👩🏼‍🦱', '👩🏼‍🦲', '👩🏼‍🦳', '👩🏼‍🦼', '👩🏼‍🦽', '👩🏽‍🌾', '👩🏽‍🍳', '👩🏽‍🍼', '👩🏽‍🎄', '👩🏽‍🎓', '👩🏽‍🎤', '👩🏽‍🎨', '👩🏽‍🏫', '👩🏽‍🏭', '👩🏽‍💻', '👩🏽‍💼', '👩🏽‍🔧', '👩🏽‍🔬', '👩🏽‍🚀', '👩🏽‍🚒', '👩🏽‍🦯', '👩🏽‍🦰', '👩🏽‍🦱', '👩🏽‍🦲', '👩🏽‍🦳', '👩🏽‍🦼', '👩🏽‍🦽', '👩🏾‍🌾', '👩🏾‍🍳', '👩🏾‍🍼', '👩🏾‍🎄', '👩🏾‍🎓', '👩🏾‍🎤', '👩🏾‍🎨', '👩🏾‍🏫', '👩🏾‍🏭', '👩🏾‍💻', '👩🏾‍💼', '👩🏾‍🔧', '👩🏾‍🔬', '👩🏾‍🚀', '👩🏾‍🚒', '👩🏾‍🦯', '👩🏾‍🦰', '👩🏾‍🦱', '👩🏾‍🦲', '👩🏾‍🦳', '👩🏾‍🦼', '👩🏾‍🦽', '👩🏿‍🌾', '👩🏿‍🍳', '👩🏿‍🍼', '👩🏿‍🎄', '👩🏿‍🎓', '👩🏿‍🎤', '👩🏿‍🎨', '👩🏿‍🏫', '👩🏿‍🏭', '👩🏿‍💻', '👩🏿‍💼', '👩🏿‍🔧', '👩🏿‍🔬', '👩🏿‍🚀', '👩🏿‍🚒', '👩🏿‍🦯', '👩🏿‍🦰', '👩🏿‍🦱', '👩🏿‍🦲', '👩🏿‍🦳', '👩🏿‍🦼', '👩🏿‍🦽', '🧑🏻‍🌾', '🧑🏻‍🍳', '🧑🏻‍🍼', '🧑🏻‍🎄', '🧑🏻‍🎓', '🧑🏻‍🎤', '🧑🏻‍🎨', '🧑🏻‍🏫', '🧑🏻‍🏭', '🧑🏻‍💻', '🧑🏻‍💼', '🧑🏻‍🔧', '🧑🏻‍🔬', '🧑🏻‍🚀', '🧑🏻‍🚒', '🧑🏻‍🦯', '🧑🏻‍🦰', '🧑🏻‍🦱', '🧑🏻‍🦲', '🧑🏻‍🦳', '🧑🏻‍🦼', '🧑🏻‍🦽', '🧑🏼‍🌾', '🧑🏼‍🍳', '🧑🏼‍🍼', '🧑🏼‍🎄', '🧑🏼‍🎓', '🧑🏼‍🎤', '🧑🏼‍🎨', '🧑🏼‍🏫', '🧑🏼‍🏭', '🧑🏼‍💻', '🧑🏼‍💼', '🧑🏼‍🔧', '🧑🏼‍🔬', '🧑🏼‍🚀', '🧑🏼‍🚒', '🧑🏼‍🦯', '🧑🏼‍🦰', '🧑🏼‍🦱', '🧑🏼‍🦲', '🧑🏼‍🦳', '🧑🏼‍🦼', '🧑🏼‍🦽', '🧑🏽‍🌾', '🧑🏽‍🍳', '🧑🏽‍🍼', '🧑🏽‍🎄', '🧑🏽‍🎓', '🧑🏽‍🎤', '🧑🏽‍🎨', '🧑🏽‍🏫', '🧑🏽‍🏭', '🧑🏽‍💻', '🧑🏽‍💼', '🧑🏽‍🔧', '🧑🏽‍🔬', '🧑🏽‍🚀', '🧑🏽‍🚒', '🧑🏽‍🦯', '🧑🏽‍🦰', '🧑🏽‍🦱', '🧑🏽‍🦲', '🧑🏽‍🦳', '🧑🏽‍🦼', '🧑🏽‍🦽', '🧑🏾‍🌾', '🧑🏾‍🍳', '🧑🏾‍🍼', '🧑🏾‍🎄', '🧑🏾‍🎓', '🧑🏾‍🎤', '🧑🏾‍🎨', '🧑🏾‍🏫', '🧑🏾‍🏭', '🧑🏾‍💻', '🧑🏾‍💼', '🧑🏾‍🔧', '🧑🏾‍🔬', '🧑🏾‍🚀', '🧑🏾‍🚒', '🧑🏾‍🦯', '🧑🏾‍🦰', '🧑🏾‍🦱', '🧑🏾‍🦲', '🧑🏾‍🦳', '🧑🏾‍🦼', '🧑🏾‍🦽', '🧑🏿‍🌾', '🧑🏿‍🍳', '🧑🏿‍🍼', '🧑🏿‍🎄', '🧑🏿‍🎓', '🧑🏿‍🎤', '🧑🏿‍🎨', '🧑🏿‍🏫', '🧑🏿‍🏭', '🧑🏿‍💻', '🧑🏿‍💼', '🧑🏿‍🔧', '🧑🏿‍🔬', '🧑🏿‍🚀', '🧑🏿‍🚒', '🧑🏿‍🦯', '🧑🏿‍🦰', '🧑🏿‍🦱', '🧑🏿‍🦲', '🧑🏿‍🦳', '🧑🏿‍🦼', '🧑🏿‍🦽', '🏳️‍🌈', '😶‍🌫️', '🏃‍♀️', '🏃‍♂️', '🏄‍♀️', '🏄‍♂️', '🏊‍♀️', '🏊‍♂️', '🏴‍☠️', '🐻‍❄️', '👨‍⚕️', '👨‍⚖️', '👨‍✈️', '👩‍⚕️', '👩‍⚖️', '👩‍✈️', '👮‍♀️', '👮‍♂️', '👯‍♀️', '👯‍♂️', '👰‍♀️', '👰‍♂️', '👱‍♀️', '👱‍♂️', '👳‍♀️', '👳‍♂️', '👷‍♀️', '👷‍♂️', '💁‍♀️', '💁‍♂️', '💂‍♀️', '💂‍♂️', '💆‍♀️', '💆‍♂️', '💇‍♀️', '💇‍♂️', '🙅‍♀️', '🙅‍♂️', '🙆‍♀️', '🙆‍♂️', '🙇‍♀️', '🙇‍♂️', '🙋‍♀️', '🙋‍♂️', '🙍‍♀️', '🙍‍♂️', '🙎‍♀️', '🙎‍♂️', '🚣‍♀️', '🚣‍♂️', '🚴‍♀️', '🚴‍♂️', '🚵‍♀️', '🚵‍♂️', '🚶‍♀️', '🚶‍♂️', '🤦‍♀️', '🤦‍♂️', '🤵‍♀️', '🤵‍♂️', '🤷‍♀️', '🤷‍♂️', '🤸‍♀️', '🤸‍♂️', '🤹‍♀️', '🤹‍♂️', '🤼‍♀️', '🤼‍♂️', '🤽‍♀️', '🤽‍♂️', '🤾‍♀️', '🤾‍♂️', '🦸‍♀️', '🦸‍♂️', '🦹‍♀️', '🦹‍♂️', '🧍‍♀️', '🧍‍♂️', '🧎‍♀️', '🧎‍♂️', '🧏‍♀️', '🧏‍♂️', '🧑‍⚕️', '🧑‍⚖️', '🧑‍✈️', '🧔‍♀️', '🧔‍♂️', '🧖‍♀️', '🧖‍♂️', '🧗‍♀️', '🧗‍♂️', '🧘‍♀️', '🧘‍♂️', '🧙‍♀️', '🧙‍♂️', '🧚‍♀️', '🧚‍♂️', '🧛‍♀️', '🧛‍♂️', '🧜‍♀️', '🧜‍♂️', '🧝‍♀️', '🧝‍♂️', '🧞‍♀️', '🧞‍♂️', '🧟‍♀️', '🧟‍♂️', '❤️‍🔥', '❤️‍🩹', '🐕‍🦺', '👁‍🗨', '👨‍🌾', '👨‍🍳', '👨‍🍼', '👨‍🎄', '👨‍🎓', '👨‍🎤', '👨‍🎨', '👨‍🏫', '👨‍🏭', '👨‍👦', '👨‍👧', '👨‍💻', '👨‍💼', '👨‍🔧', '👨‍🔬', '👨‍🚀', '👨‍🚒', '👨‍🦯', '👨‍🦰', '👨‍🦱', '👨‍🦲', '👨‍🦳', '👨‍🦼', '👨‍🦽', '👩‍🌾', '👩‍🍳', '👩‍🍼', '👩‍🎄', '👩‍🎓', '👩‍🎤', '👩‍🎨', '👩‍🏫', '👩‍🏭', '👩‍👦', '👩‍👧', '👩‍💻', '👩‍💼', '👩‍🔧', '👩‍🔬', '👩‍🚀', '👩‍🚒', '👩‍🦯', '👩‍🦰', '👩‍🦱', '👩‍🦲', '👩‍🦳', '👩‍🦼', '👩‍🦽', '😮‍💨', '😵‍💫', '🧑‍🌾', '🧑‍🍳', '🧑‍🍼', '🧑‍🎄', '🧑‍🎓', '🧑‍🎤', '🧑‍🎨', '🧑‍🏫', '🧑‍🏭', '🧑‍💻', '🧑‍💼', '🧑‍🔧', '🧑‍🔬', '🧑‍🚀', '🧑‍🚒', '🧑‍🦯', '🧑‍🦰', '🧑‍🦱', '🧑‍🦲', '🧑‍🦳', '🧑‍🦼', '🧑‍🦽', '🐈‍⬛', '🐦‍⬛', '🇦🇨', '🇦🇩', '🇦🇪', '🇦🇫', '🇦🇬', '🇦🇮', '🇦🇱', '🇦🇲', '🇦🇴', '🇦🇶', '🇦🇷', '🇦🇸', '🇦🇹', '🇦🇺', '🇦🇼', '🇦🇽', '🇦🇿', '🇧🇦', '🇧🇧', '🇧🇩', '🇧🇪', '🇧🇫', '🇧🇬', '🇧🇭', '🇧🇮', '🇧🇯', '🇧🇱', '🇧🇲', '🇧🇳', '🇧🇴', '🇧🇶', '🇧🇷', '🇧🇸', '🇧🇹', '🇧🇻', '🇧🇼', '🇧🇾', '🇧🇿', '🇨🇦', '🇨🇨', '🇨🇩', '🇨🇫', '🇨🇬', '🇨🇭', '🇨🇮', '🇨🇰', '🇨🇱', '🇨🇲', '🇨🇳', '🇨🇴', '🇨🇵', '🇨🇷', '🇨🇺', '🇨🇻', '🇨🇼', '🇨🇽', '🇨🇾', '🇨🇿', '🇩🇪', '🇩🇬', '🇩🇯', '🇩🇰', '🇩🇲', '🇩🇴', '🇩🇿', '🇪🇦', '🇪🇨', '🇪🇪', '🇪🇬', '🇪🇭', '🇪🇷', '🇪🇸', '🇪🇹', '🇪🇺', '🇫🇮', '🇫🇯', '🇫🇰', '🇫🇲', '🇫🇴', '🇫🇷', '🇬🇦', '🇬🇧', '🇬🇩', '🇬🇪', '🇬🇫', '🇬🇬', '🇬🇭', '🇬🇮', '🇬🇱', '🇬🇲', '🇬🇳', '🇬🇵', '🇬🇶', '🇬🇷', '🇬🇸', '🇬🇹', '🇬🇺', '🇬🇼', '🇬🇾', '🇭🇰', '🇭🇲', '🇭🇳', '🇭🇷', '🇭🇹', '🇭🇺', '🇮🇨', '🇮🇩', '🇮🇪', '🇮🇱', '🇮🇲', '🇮🇳', '🇮🇴', '🇮🇶', '🇮🇷', '🇮🇸', '🇮🇹', '🇯🇪', '🇯🇲', '🇯🇴', '🇯🇵', '🇰🇪', '🇰🇬', '🇰🇭', '🇰🇮', '🇰🇲', '🇰🇳', '🇰🇵', '🇰🇷', '🇰🇼', '🇰🇾', '🇰🇿', '🇱🇦', '🇱🇧', '🇱🇨', '🇱🇮', '🇱🇰', '🇱🇷', '🇱🇸', '🇱🇹', '🇱🇺', '🇱🇻', '🇱🇾', '🇲🇦', '🇲🇨', '🇲🇩', '🇲🇪', '🇲🇫', '🇲🇬', '🇲🇭', '🇲🇰', '🇲🇱', '🇲🇲', '🇲🇳', '🇲🇴', '🇲🇵', '🇲🇶', '🇲🇷', '🇲🇸', '🇲🇹', '🇲🇺', '🇲🇻', '🇲🇼', '🇲🇽', '🇲🇾', '🇲🇿', '🇳🇦', '🇳🇨', '🇳🇪', '🇳🇫', '🇳🇬', '🇳🇮', '🇳🇱', '🇳🇴', '🇳🇵', '🇳🇷', '🇳🇺', '🇳🇿', '🇴🇲', '🇵🇦', '🇵🇪', '🇵🇫', '🇵🇬', '🇵🇭', '🇵🇰', '🇵🇱', '🇵🇲', '🇵🇳', '🇵🇷', '🇵🇸', '🇵🇹', '🇵🇼', '🇵🇾', '🇶🇦', '🇷🇪', '🇷🇴', '🇷🇸', '🇷🇺', '🇷🇼', '🇸🇦', '🇸🇧', '🇸🇨', '🇸🇩', '🇸🇪', '🇸🇬', '🇸🇭', '🇸🇮', '🇸🇯', '🇸🇰', '🇸🇱', '🇸🇲', '🇸🇳', '🇸🇴', '🇸🇷', '🇸🇸', '🇸🇹', '🇸🇻', '🇸🇽', '🇸🇾', '🇸🇿', '🇹🇦', '🇹🇨', '🇹🇩', '🇹🇫', '🇹🇬', '🇹🇭', '🇹🇯', '🇹🇰', '🇹🇱', '🇹🇲', '🇹🇳', '🇹🇴', '🇹🇷', '🇹🇹', '🇹🇻', '🇹🇼', '🇹🇿', '🇺🇦', '🇺🇬', '🇺🇲', '🇺🇳', '🇺🇸', '🇺🇾', '🇺🇿', '🇻🇦', '🇻🇨', '🇻🇪', '🇻🇬', '🇻🇮', '🇻🇳', '🇻🇺', '🇼🇫', '🇼🇸', '🇽🇰', '🇾🇪', '🇾🇹', '🇿🇦', '🇿🇲', '🇿🇼', '🎅🏻', '🎅🏼', '🎅🏽', '🎅🏾', '🎅🏿', '🏂🏻', '🏂🏼', '🏂🏽', '🏂🏾', '🏂🏿', '🏃🏻', '🏃🏼', '🏃🏽', '🏃🏾', '🏃🏿', '🏄🏻', '🏄🏼', '🏄🏽', '🏄🏾', '🏄🏿', '🏇🏻', '🏇🏼', '🏇🏽', '🏇🏾', '🏇🏿', '🏊🏻', '🏊🏼', '🏊🏽', '🏊🏾', '🏊🏿', '🏋🏻', '🏋🏼', '🏋🏽', '🏋🏾', '🏋🏿', '🏌🏻', '🏌🏼', '🏌🏽', '🏌🏾', '🏌🏿', '👂🏻', '👂🏼', '👂🏽', '👂🏾', '👂🏿', '👃🏻', '👃🏼', '👃🏽', '👃🏾', '👃🏿', '👆🏻', '👆🏼', '👆🏽', '👆🏾', '👆🏿', '👇🏻', '👇🏼', '👇🏽', '👇🏾', '👇🏿', '👈🏻', '👈🏼', '👈🏽', '👈🏾', '👈🏿', '👉🏻', '👉🏼', '👉🏽', '👉🏾', '👉🏿', '👊🏻', '👊🏼', '👊🏽', '👊🏾', '👊🏿', '👋🏻', '👋🏼', '👋🏽', '👋🏾', '👋🏿', '👌🏻', '👌🏼', '👌🏽', '👌🏾', '👌🏿', '👍🏻', '👍🏼', '👍🏽', '👍🏾', '👍🏿', '👎🏻', '👎🏼', '👎🏽', '👎🏾', '👎🏿', '👏🏻', '👏🏼', '👏🏽', '👏🏾', '👏🏿', '👐🏻', '👐🏼', '👐🏽', '👐🏾', '👐🏿', '👦🏻', '👦🏼', '👦🏽', '👦🏾', '👦🏿', '👧🏻', '👧🏼', '👧🏽', '👧🏾', '👧🏿', '👨🏻', '👨🏼', '👨🏽', '👨🏾', '👨🏿', '👩🏻', '👩🏼', '👩🏽', '👩🏾', '👩🏿', '👫🏻', '👫🏼', '👫🏽', '👫🏾', '👫🏿', '👬🏻', '👬🏼', '👬🏽', '👬🏾', '👬🏿', '👭🏻', '👭🏼', '👭🏽', '👭🏾', '👭🏿', '👮🏻', '👮🏼', '👮🏽', '👮🏾', '👮🏿', '👰🏻', '👰🏼', '👰🏽', '👰🏾', '👰🏿', '👱🏻', '👱🏼', '👱🏽', '👱🏾', '👱🏿', '👲🏻', '👲🏼', '👲🏽', '👲🏾', '👲🏿', '👳🏻', '👳🏼', '👳🏽', '👳🏾', '👳🏿', '👴🏻', '👴🏼', '👴🏽', '👴🏾', '👴🏿', '👵🏻', '👵🏼', '👵🏽', '👵🏾', '👵🏿', '👶🏻', '👶🏼', '👶🏽', '👶🏾', '👶🏿', '👷🏻', '👷🏼', '👷🏽', '👷🏾', '👷🏿', '👸🏻', '👸🏼', '👸🏽', '👸🏾', '👸🏿', '👼🏻', '👼🏼', '👼🏽', '👼🏾', '👼🏿', '💁🏻', '💁🏼', '💁🏽', '💁🏾', '💁🏿', '💂🏻', '💂🏼', '💂🏽', '💂🏾', '💂🏿', '💃🏻', '💃🏼', '💃🏽', '💃🏾', '💃🏿', '💅🏻', '💅🏼', '💅🏽', '💅🏾', '💅🏿', '💆🏻', '💆🏼', '💆🏽', '💆🏾', '💆🏿', '💇🏻', '💇🏼', '💇🏽', '💇🏾', '💇🏿', '💏🏻', '💏🏼', '💏🏽', '💏🏾', '💏🏿', '💑🏻', '💑🏼', '💑🏽', '💑🏾', '💑🏿', '💪🏻', '💪🏼', '💪🏽', '💪🏾', '💪🏿', '🕴🏻', '🕴🏼', '🕴🏽', '🕴🏾', '🕴🏿', '🕵🏻', '🕵🏼', '🕵🏽', '🕵🏾', '🕵🏿', '🕺🏻', '🕺🏼', '🕺🏽', '🕺🏾', '🕺🏿', '🖐🏻', '🖐🏼', '🖐🏽', '🖐🏾', '🖐🏿', '🖕🏻', '🖕🏼', '🖕🏽', '🖕🏾', '🖕🏿', '🖖🏻', '🖖🏼', '🖖🏽', '🖖🏾', '🖖🏿', '🙅🏻', '🙅🏼', '🙅🏽', '🙅🏾', '🙅🏿', '🙆🏻', '🙆🏼', '🙆🏽', '🙆🏾', '🙆🏿', '🙇🏻', '🙇🏼', '🙇🏽', '🙇🏾', '🙇🏿', '🙋🏻', '🙋🏼', '🙋🏽', '🙋🏾', '🙋🏿', '🙌🏻', '🙌🏼', '🙌🏽', '🙌🏾', '🙌🏿', '🙍🏻', '🙍🏼', '🙍🏽', '🙍🏾', '🙍🏿', '🙎🏻', '🙎🏼', '🙎🏽', '🙎🏾', '🙎🏿', '🙏🏻', '🙏🏼', '🙏🏽', '🙏🏾', '🙏🏿', '🚣🏻', '🚣🏼', '🚣🏽', '🚣🏾', '🚣🏿', '🚴🏻', '🚴🏼', '🚴🏽', '🚴🏾', '🚴🏿', '🚵🏻', '🚵🏼', '🚵🏽', '🚵🏾', '🚵🏿', '🚶🏻', '🚶🏼', '🚶🏽', '🚶🏾', '🚶🏿', '🛀🏻', '🛀🏼', '🛀🏽', '🛀🏾', '🛀🏿', '🛌🏻', '🛌🏼', '🛌🏽', '🛌🏾', '🛌🏿', '🤌🏻', '🤌🏼', '🤌🏽', '🤌🏾', '🤌🏿', '🤏🏻', '🤏🏼', '🤏🏽', '🤏🏾', '🤏🏿', '🤘🏻', '🤘🏼', '🤘🏽', '🤘🏾', '🤘🏿', '🤙🏻', '🤙🏼', '🤙🏽', '🤙🏾', '🤙🏿', '🤚🏻', '🤚🏼', '🤚🏽', '🤚🏾', '🤚🏿', '🤛🏻', '🤛🏼', '🤛🏽', '🤛🏾', '🤛🏿', '🤜🏻', '🤜🏼', '🤜🏽', '🤜🏾', '🤜🏿', '🤝🏻', '🤝🏼', '🤝🏽', '🤝🏾', '🤝🏿', '🤞🏻', '🤞🏼', '🤞🏽', '🤞🏾', '🤞🏿', '🤟🏻', '🤟🏼', '🤟🏽', '🤟🏾', '🤟🏿', '🤦🏻', '🤦🏼', '🤦🏽', '🤦🏾', '🤦🏿', '🤰🏻', '🤰🏼', '🤰🏽', '🤰🏾', '🤰🏿', '🤱🏻', '🤱🏼', '🤱🏽', '🤱🏾', '🤱🏿', '🤲🏻', '🤲🏼', '🤲🏽', '🤲🏾', '🤲🏿', '🤳🏻', '🤳🏼', '🤳🏽', '🤳🏾', '🤳🏿', '🤴🏻', '🤴🏼', '🤴🏽', '🤴🏾', '🤴🏿', '🤵🏻', '🤵🏼', '🤵🏽', '🤵🏾', '🤵🏿', '🤶🏻', '🤶🏼', '🤶🏽', '🤶🏾', '🤶🏿', '🤷🏻', '🤷🏼', '🤷🏽', '🤷🏾', '🤷🏿', '🤸🏻', '🤸🏼', '🤸🏽', '🤸🏾', '🤸🏿', '🤹🏻', '🤹🏼', '🤹🏽', '🤹🏾', '🤹🏿', '🤽🏻', '🤽🏼', '🤽🏽', '🤽🏾', '🤽🏿', '🤾🏻', '🤾🏼', '🤾🏽', '🤾🏾', '🤾🏿', '🥷🏻', '🥷🏼', '🥷🏽', '🥷🏾', '🥷🏿', '🦵🏻', '🦵🏼', '🦵🏽', '🦵🏾', '🦵🏿', '🦶🏻', '🦶🏼', '🦶🏽', '🦶🏾', '🦶🏿', '🦸🏻', '🦸🏼', '🦸🏽', '🦸🏾', '🦸🏿', '🦹🏻', '🦹🏼', '🦹🏽', '🦹🏾', '🦹🏿', '🦻🏻', '🦻🏼', '🦻🏽', '🦻🏾', '🦻🏿', '🧍🏻', '🧍🏼', '🧍🏽', '🧍🏾', '🧍🏿', '🧎🏻', '🧎🏼', '🧎🏽', '🧎🏾', '🧎🏿', '🧏🏻', '🧏🏼', '🧏🏽', '🧏🏾', '🧏🏿', '🧑🏻', '🧑🏼', '🧑🏽', '🧑🏾', '🧑🏿', '🧒🏻', '🧒🏼', '🧒🏽', '🧒🏾', '🧒🏿', '🧓🏻', '🧓🏼', '🧓🏽', '🧓🏾', '🧓🏿', '🧔🏻', '🧔🏼', '🧔🏽', '🧔🏾', '🧔🏿', '🧕🏻', '🧕🏼', '🧕🏽', '🧕🏾', '🧕🏿', '🧖🏻', '🧖🏼', '🧖🏽', '🧖🏾', '🧖🏿', '🧗🏻', '🧗🏼', '🧗🏽', '🧗🏾', '🧗🏿', '🧘🏻', '🧘🏼', '🧘🏽', '🧘🏾', '🧘🏿', '🧙🏻', '🧙🏼', '🧙🏽', '🧙🏾', '🧙🏿', '🧚🏻', '🧚🏼', '🧚🏽', '🧚🏾', '🧚🏿', '🧛🏻', '🧛🏼', '🧛🏽', '🧛🏾', '🧛🏿', '🧜🏻', '🧜🏼', '🧜🏽', '🧜🏾', '🧜🏿', '🧝🏻', '🧝🏼', '🧝🏽', '🧝🏾', '🧝🏿', '🫃🏻', '🫃🏼', '🫃🏽', '🫃🏾', '🫃🏿', '🫄🏻', '🫄🏼', '🫄🏽', '🫄🏾', '🫄🏿', '🫅🏻', '🫅🏼', '🫅🏽', '🫅🏾', '🫅🏿', '🫰🏻', '🫰🏼', '🫰🏽', '🫰🏾', '🫰🏿', '🫱🏻', '🫱🏼', '🫱🏽', '🫱🏾', '🫱🏿', '🫲🏻', '🫲🏼', '🫲🏽', '🫲🏾', '🫲🏿', '🫳🏻', '🫳🏼', '🫳🏽', '🫳🏾', '🫳🏿', '🫴🏻', '🫴🏼', '🫴🏽', '🫴🏾', '🫴🏿', '🫵🏻', '🫵🏼', '🫵🏽', '🫵🏾', '🫵🏿', '🫶🏻', '🫶🏼', '🫶🏽', '🫶🏾', '🫶🏿', '🫷🏻', '🫷🏼', '🫷🏽', '🫷🏾', '🫷🏿', '🫸🏻', '🫸🏼', '🫸🏽', '🫸🏾', '🫸🏿', '☝🏻', '☝🏼', '☝🏽', '☝🏾', '☝🏿', '⛷🏻', '⛷🏼', '⛷🏽', '⛷🏾', '⛷🏿', '⛹🏻', '⛹🏼', '⛹🏽', '⛹🏾', '⛹🏿', '✊🏻', '✊🏼', '✊🏽', '✊🏾', '✊🏿', '✋🏻', '✋🏼', '✋🏽', '✋🏾', '✋🏿', '✌🏻', '✌🏼', '✌🏽', '✌🏾', '✌🏿', '✍🏻', '✍🏼', '✍🏽', '✍🏾', '✍🏿', '#⃣', '*⃣', '0⃣', '1⃣', '2⃣', '3⃣', '4⃣', '5⃣', '6⃣', '7⃣', '8⃣', '9⃣', '🀄', '🃏', '🅰', '🅱', '🅾', '🅿', '🆎', '🆑', '🆒', '🆓', '🆔', '🆕', '🆖', '🆗', '🆘', '🆙', '🆚', '🇦', '🇧', '🇨', '🇩', '🇪', '🇫', '🇬', '🇭', '🇮', '🇯', '🇰', '🇱', '🇲', '🇳', '🇴', '🇵', '🇶', '🇷', '🇸', '🇹', '🇺', '🇻', '🇼', '🇽', '🇾', '🇿', '🈁', '🈂', '🈚', '🈯', '🈲', '🈳', '🈴', '🈵', '🈶', '🈷', '🈸', '🈹', '🈺', '🉐', '🉑', '🌀', '🌁', '🌂', '🌃', '🌄', '🌅', '🌆', '🌇', '🌈', '🌉', '🌊', '🌋', '🌌', '🌍', '🌎', '🌏', '🌐', '🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘', '🌙', '🌚', '🌛', '🌜', '🌝', '🌞', '🌟', '🌠', '🌡', '🌤', '🌥', '🌦', '🌧', '🌨', '🌩', '🌪', '🌫', '🌬', '🌭', '🌮', '🌯', '🌰', '🌱', '🌲', '🌳', '🌴', '🌵', '🌶', '🌷', '🌸', '🌹', '🌺', '🌻', '🌼', '🌽', '🌾', '🌿', '🍀', '🍁', '🍂', '🍃', '🍄', '🍅', '🍆', '🍇', '🍈', '🍉', '🍊', '🍋', '🍌', '🍍', '🍎', '🍏', '🍐', '🍑', '🍒', '🍓', '🍔', '🍕', '🍖', '🍗', '🍘', '🍙', '🍚', '🍛', '🍜', '🍝', '🍞', '🍟', '🍠', '🍡', '🍢', '🍣', '🍤', '🍥', '🍦', '🍧', '🍨', '🍩', '🍪', '🍫', '🍬', '🍭', '🍮', '🍯', '🍰', '🍱', '🍲', '🍳', '🍴', '🍵', '🍶', '🍷', '🍸', '🍹', '🍺', '🍻', '🍼', '🍽', '🍾', '🍿', '🎀', '🎁', '🎂', '🎃', '🎄', '🎅', '🎆', '🎇', '🎈', '🎉', '🎊', '🎋', '🎌', '🎍', '🎎', '🎏', '🎐', '🎑', '🎒', '🎓', '🎖', '🎗', '🎙', '🎚', '🎛', '🎞', '🎟', '🎠', '🎡', '🎢', '🎣', '🎤', '🎥', '🎦', '🎧', '🎨', '🎩', '🎪', '🎫', '🎬', '🎭', '🎮', '🎯', '🎰', '🎱', '🎲', '🎳', '🎴', '🎵', '🎶', '🎷', '🎸', '🎹', '🎺', '🎻', '🎼', '🎽', '🎾', '🎿', '🏀', '🏁', '🏂', '🏃', '🏄', '🏅', '🏆', '🏇', '🏈', '🏉', '🏊', '🏋', '🏌', '🏍', '🏎', '🏏', '🏐', '🏑', '🏒', '🏓', '🏔', '🏕', '🏖', '🏗', '🏘', '🏙', '🏚', '🏛', '🏜', '🏝', '🏞', '🏟', '🏠', '🏡', '🏢', '🏣', '🏤', '🏥', '🏦', '🏧', '🏨', '🏩', '🏪', '🏫', '🏬', '🏭', '🏮', '🏯', '🏰', '🏳', '🏴', '🏵', '🏷', '🏸', '🏹', '🏺', '🏻', '🏼', '🏽', '🏾', '🏿', '🐀', '🐁', '🐂', '🐃', '🐄', '🐅', '🐆', '🐇', '🐈', '🐉', '🐊', '🐋', '🐌', '🐍', '🐎', '🐏', '🐐', '🐑', '🐒', '🐓', '🐔', '🐕', '🐖', '🐗', '🐘', '🐙', '🐚', '🐛', '🐜', '🐝', '🐞', '🐟', '🐠', '🐡', '🐢', '🐣', '🐤', '🐥', '🐦', '🐧', '🐨', '🐩', '🐪', '🐫', '🐬', '🐭', '🐮', '🐯', '🐰', '🐱', '🐲', '🐳', '🐴', '🐵', '🐶', '🐷', '🐸', '🐹', '🐺', '🐻', '🐼', '🐽', '🐾', '🐿', '👀', '👁', '👂', '👃', '👄', '👅', '👆', '👇', '👈', '👉', '👊', '👋', '👌', '👍', '👎', '👏', '👐', '👑', '👒', '👓', '👔', '👕', '👖', '👗', '👘', '👙', '👚', '👛', '👜', '👝', '👞', '👟', '👠', '👡', '👢', '👣', '👤', '👥', '👦', '👧', '👨', '👩', '👪', '👫', '👬', '👭', '👮', '👯', '👰', '👱', '👲', '👳', '👴', '👵', '👶', '👷', '👸', '👹', '👺', '👻', '👼', '👽', '👾', '👿', '💀', '💁', '💂', '💃', '💄', '💅', '💆', '💇', '💈', '💉', '💊', '💋', '💌', '💍', '💎', '💏', '💐', '💑', '💒', '💓', '💔', '💕', '💖', '💗', '💘', '💙', '💚', '💛', '💜', '💝', '💞', '💟', '💠', '💡', '💢', '💣', '💤', '💥', '💦', '💧', '💨', '💩', '💪', '💫', '💬', '💭', '💮', '💯', '💰', '💱', '💲', '💳', '💴', '💵', '💶', '💷', '💸', '💹', '💺', '💻', '💼', '💽', '💾', '💿', '📀', '📁', '📂', '📃', '📄', '📅', '📆', '📇', '📈', '📉', '📊', '📋', '📌', '📍', '📎', '📏', '📐', '📑', '📒', '📓', '📔', '📕', '📖', '📗', '📘', '📙', '📚', '📛', '📜', '📝', '📞', '📟', '📠', '📡', '📢', '📣', '📤', '📥', '📦', '📧', '📨', '📩', '📪', '📫', '📬', '📭', '📮', '📯', '📰', '📱', '📲', '📳', '📴', '📵', '📶', '📷', '📸', '📹', '📺', '📻', '📼', '📽', '📿', '🔀', '🔁', '🔂', '🔃', '🔄', '🔅', '🔆', '🔇', '🔈', '🔉', '🔊', '🔋', '🔌', '🔍', '🔎', '🔏', '🔐', '🔑', '🔒', '🔓', '🔔', '🔕', '🔖', '🔗', '🔘', '🔙', '🔚', '🔛', '🔜', '🔝', '🔞', '🔟', '🔠', '🔡', '🔢', '🔣', '🔤', '🔥', '🔦', '🔧', '🔨', '🔩', '🔪', '🔫', '🔬', '🔭', '🔮', '🔯', '🔰', '🔱', '🔲', '🔳', '🔴', '🔵', '🔶', '🔷', '🔸', '🔹', '🔺', '🔻', '🔼', '🔽', '🕉', '🕊', '🕋', '🕌', '🕍', '🕎', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚', '🕛', '🕜', '🕝', '🕞', '🕟', '🕠', '🕡', '🕢', '🕣', '🕤', '🕥', '🕦', '🕧', '🕯', '🕰', '🕳', '🕴', '🕵', '🕶', '🕷', '🕸', '🕹', '🕺', '🖇', '🖊', '🖋', '🖌', '🖍', '🖐', '🖕', '🖖', '🖤', '🖥', '🖨', '🖱', '🖲', '🖼', '🗂', '🗃', '🗄', '🗑', '🗒', '🗓', '🗜', '🗝', '🗞', '🗡', '🗣', '🗨', '🗯', '🗳', '🗺', '🗻', '🗼', '🗽', '🗾', '🗿', '😀', '😁', '😂', '😃', '😄', '😅', '😆', '😇', '😈', '😉', '😊', '😋', '😌', '😍', '😎', '😏', '😐', '😑', '😒', '😓', '😔', '😕', '😖', '😗', '😘', '😙', '😚', '😛', '😜', '😝', '😞', '😟', '😠', '😡', '😢', '😣', '😤', '😥', '😦', '😧', '😨', '😩', '😪', '😫', '😬', '😭', '😮', '😯', '😰', '😱', '😲', '😳', '😴', '😵', '😶', '😷', '😸', '😹', '😺', '😻', '😼', '😽', '😾', '😿', '🙀', '🙁', '🙂', '🙃', '🙄', '🙅', '🙆', '🙇', '🙈', '🙉', '🙊', '🙋', '🙌', '🙍', '🙎', '🙏', '🚀', '🚁', '🚂', '🚃', '🚄', '🚅', '🚆', '🚇', '🚈', '🚉', '🚊', '🚋', '🚌', '🚍', '🚎', '🚏', '🚐', '🚑', '🚒', '🚓', '🚔', '🚕', '🚖', '🚗', '🚘', '🚙', '🚚', '🚛', '🚜', '🚝', '🚞', '🚟', '🚠', '🚡', '🚢', '🚣', '🚤', '🚥', '🚦', '🚧', '🚨', '🚩', '🚪', '🚫', '🚬', '🚭', '🚮', '🚯', '🚰', '🚱', '🚲', '🚳', '🚴', '🚵', '🚶', '🚷', '🚸', '🚹', '🚺', '🚻', '🚼', '🚽', '🚾', '🚿', '🛀', '🛁', '🛂', '🛃', '🛄', '🛅', '🛋', '🛌', '🛍', '🛎', '🛏', '🛐', '🛑', '🛒', '🛕', '🛖', '🛗', '🛜', '🛝', '🛞', '🛟', '🛠', '🛡', '🛢', '🛣', '🛤', '🛥', '🛩', '🛫', '🛬', '🛰', '🛳', '🛴', '🛵', '🛶', '🛷', '🛸', '🛹', '🛺', '🛻', '🛼', '🟠', '🟡', '🟢', '🟣', '🟤', '🟥', '🟦', '🟧', '🟨', '🟩', '🟪', '🟫', '🟰', '🤌', '🤍', '🤎', '🤏', '🤐', '🤑', '🤒', '🤓', '🤔', '🤕', '🤖', '🤗', '🤘', '🤙', '🤚', '🤛', '🤜', '🤝', '🤞', '🤟', '🤠', '🤡', '🤢', '🤣', '🤤', '🤥', '🤦', '🤧', '🤨', '🤩', '🤪', '🤫', '🤬', '🤭', '🤮', '🤯', '🤰', '🤱', '🤲', '🤳', '🤴', '🤵', '🤶', '🤷', '🤸', '🤹', '🤺', '🤼', '🤽', '🤾', '🤿', '🥀', '🥁', '🥂', '🥃', '🥄', '🥅', '🥇', '🥈', '🥉', '🥊', '🥋', '🥌', '🥍', '🥎', '🥏', '🥐', '🥑', '🥒', '🥓', '🥔', '🥕', '🥖', '🥗', '🥘', '🥙', '🥚', '🥛', '🥜', '🥝', '🥞', '🥟', '🥠', '🥡', '🥢', '🥣', '🥤', '🥥', '🥦', '🥧', '🥨', '🥩', '🥪', '🥫', '🥬', '🥭', '🥮', '🥯', '🥰', '🥱', '🥲', '🥳', '🥴', '🥵', '🥶', '🥷', '🥸', '🥹', '🥺', '🥻', '🥼', '🥽', '🥾', '🥿', '🦀', '🦁', '🦂', '🦃', '🦄', '🦅', '🦆', '🦇', '🦈', '🦉', '🦊', '🦋', '🦌', '🦍', '🦎', '🦏', '🦐', '🦑', '🦒', '🦓', '🦔', '🦕', '🦖', '🦗', '🦘', '🦙', '🦚', '🦛', '🦜', '🦝', '🦞', '🦟', '🦠', '🦡', '🦢', '🦣', '🦤', '🦥', '🦦', '🦧', '🦨', '🦩', '🦪', '🦫', '🦬', '🦭', '🦮', '🦯', '🦰', '🦱', '🦲', '🦳', '🦴', '🦵', '🦶', '🦷', '🦸', '🦹', '🦺', '🦻', '🦼', '🦽', '🦾', '🦿', '🧀', '🧁', '🧂', '🧃', '🧄', '🧅', '🧆', '🧇', '🧈', '🧉', '🧊', '🧋', '🧌', '🧍', '🧎', '🧏', '🧐', '🧑', '🧒', '🧓', '🧔', '🧕', '🧖', '🧗', '🧘', '🧙', '🧚', '🧛', '🧜', '🧝', '🧞', '🧟', '🧠', '🧡', '🧢', '🧣', '🧤', '🧥', '🧦', '🧧', '🧨', '🧩', '🧪', '🧫', '🧬', '🧭', '🧮', '🧯', '🧰', '🧱', '🧲', '🧳', '🧴', '🧵', '🧶', '🧷', '🧸', '🧹', '🧺', '🧻', '🧼', '🧽', '🧾', '🧿', '🩰', '🩱', '🩲', '🩳', '🩴', '🩵', '🩶', '🩷', '🩸', '🩹', '🩺', '🩻', '🩼', '🪀', '🪁', '🪂', '🪃', '🪄', '🪅', '🪆', '🪇', '🪈', '🪐', '🪑', '🪒', '🪓', '🪔', '🪕', '🪖', '🪗', '🪘', '🪙', '🪚', '🪛', '🪜', '🪝', '🪞', '🪟', '🪠', '🪡', '🪢', '🪣', '🪤', '🪥', '🪦', '🪧', '🪨', '🪩', '🪪', '🪫', '🪬', '🪭', '🪮', '🪯', '🪰', '🪱', '🪲', '🪳', '🪴', '🪵', '🪶', '🪷', '🪸', '🪹', '🪺', '🪻', '🪼', '🪽', '🪿', '🫀', '🫁', '🫂', '🫃', '🫄', '🫅', '🫎', '🫏', '🫐', '🫑', '🫒', '🫓', '🫔', '🫕', '🫖', '🫗', '🫘', '🫙', '🫚', '🫛', '🫠', '🫡', '🫢', '🫣', '🫤', '🫥', '🫦', '🫧', '🫨', '🫰', '🫱', '🫲', '🫳', '🫴', '🫵', '🫶', '🫷', '🫸', '‼', '⁉', '™', 'ℹ', '↔', '↕', '↖', '↗', '↘', '↙', '↩', '↪', '⌚', '⌛', '⌨', '⏏', '⏩', '⏪', '⏫', '⏬', '⏭', '⏮', '⏯', '⏰', '⏱', '⏲', '⏳', '⏸', '⏹', '⏺', 'Ⓜ', '▪', '▫', '▶', '◀', '◻', '◼', '◽', '◾', '☀', '☁', '☂', '☃', '☄', '☎', '☑', '☔', '☕', '☘', '☝', '☠', '☢', '☣', '☦', '☪', '☮', '☯', '☸', '☹', '☺', '♀', '♂', '♈', '♉', '♊', '♋', '♌', '♍', '♎', '♏', '♐', '♑', '♒', '♓', '♟', '♠', '♣', '♥', '♦', '♨', '♻', '♾', '♿', '⚒', '⚓', '⚔', '⚕', '⚖', '⚗', '⚙', '⚛', '⚜', '⚠', '⚡', '⚧', '⚪', '⚫', '⚰', '⚱', '⚽', '⚾', '⛄', '⛅', '⛈', '⛎', '⛏', '⛑', '⛓', '⛔', '⛩', '⛪', '⛰', '⛱', '⛲', '⛳', '⛴', '⛵', '⛷', '⛸', '⛹', '⛺', '⛽', '✂', '✅', '✈', '✉', '✊', '✋', '✌', '✍', '✏', '✒', '✔', '✖', '✝', '✡', '✨', '✳', '✴', '❄', '❇', '❌', '❎', '❓', '❔', '❕', '❗', '❣', '❤', '➕', '➖', '➗', '➡', '➰', '➿', '⤴', '⤵', '⬅', '⬆', '⬇', '⬛', '⬜', '⭐', '⭕', '〰', '〽', '㊗', '㊙', '' ); $partials = array( '🀄', '🃏', '🅰', '🅱', '🅾', '🅿', '🆎', '🆑', '🆒', '🆓', '🆔', '🆕', '🆖', '🆗', '🆘', '🆙', '🆚', '🇦', '🇨', '🇩', '🇪', '🇫', '🇬', '🇮', '🇱', '🇲', '🇴', '🇶', '🇷', '🇸', '🇹', '🇺', '🇼', '🇽', '🇿', '🇧', '🇭', '🇯', '🇳', '🇻', '🇾', '🇰', '🇵', '🈁', '🈂', '🈚', '🈯', '🈲', '🈳', '🈴', '🈵', '🈶', '🈷', '🈸', '🈹', '🈺', '🉐', '🉑', '🌀', '🌁', '🌂', '🌃', '🌄', '🌅', '🌆', '🌇', '🌈', '🌉', '🌊', '🌋', '🌌', '🌍', '🌎', '🌏', '🌐', '🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘', '🌙', '🌚', '🌛', '🌜', '🌝', '🌞', '🌟', '🌠', '🌡', '🌤', '🌥', '🌦', '🌧', '🌨', '🌩', '🌪', '🌫', '🌬', '🌭', '🌮', '🌯', '🌰', '🌱', '🌲', '🌳', '🌴', '🌵', '🌶', '🌷', '🌸', '🌹', '🌺', '🌻', '🌼', '🌽', '🌾', '🌿', '🍀', '🍁', '🍂', '🍃', '🍄', '🍅', '🍆', '🍇', '🍈', '🍉', '🍊', '🍋', '🍌', '🍍', '🍎', '🍏', '🍐', '🍑', '🍒', '🍓', '🍔', '🍕', '🍖', '🍗', '🍘', '🍙', '🍚', '🍛', '🍜', '🍝', '🍞', '🍟', '🍠', '🍡', '🍢', '🍣', '🍤', '🍥', '🍦', '🍧', '🍨', '🍩', '🍪', '🍫', '🍬', '🍭', '🍮', '🍯', '🍰', '🍱', '🍲', '🍳', '🍴', '🍵', '🍶', '🍷', '🍸', '🍹', '🍺', '🍻', '🍼', '🍽', '🍾', '🍿', '🎀', '🎁', '🎂', '🎃', '🎄', '🎅', '🏻', '🏼', '🏽', '🏾', '🏿', '🎆', '🎇', '🎈', '🎉', '🎊', '🎋', '🎌', '🎍', '🎎', '🎏', '🎐', '🎑', '🎒', '🎓', '🎖', '🎗', '🎙', '🎚', '🎛', '🎞', '🎟', '🎠', '🎡', '🎢', '🎣', '🎤', '🎥', '🎦', '🎧', '🎨', '🎩', '🎪', '🎫', '🎬', '🎭', '🎮', '🎯', '🎰', '🎱', '🎲', '🎳', '🎴', '🎵', '🎶', '🎷', '🎸', '🎹', '🎺', '🎻', '🎼', '🎽', '🎾', '🎿', '🏀', '🏁', '🏂', '🏃', '‍', '♀', '️', '♂', '🏄', '🏅', '🏆', '🏇', '🏈', '🏉', '🏊', '🏋', '🏌', '🏍', '🏎', '🏏', '🏐', '🏑', '🏒', '🏓', '🏔', '🏕', '🏖', '🏗', '🏘', '🏙', '🏚', '🏛', '🏜', '🏝', '🏞', '🏟', '🏠', '🏡', '🏢', '🏣', '🏤', '🏥', '🏦', '🏧', '🏨', '🏩', '🏪', '🏫', '🏬', '🏭', '🏮', '🏯', '🏰', '🏳', '⚧', '🏴', '☠', '󠁧', '󠁢', '󠁥', '󠁮', '󠁿', '󠁳', '󠁣', '󠁴', '󠁷', '󠁬', '🏵', '🏷', '🏸', '🏹', '🏺', '🐀', '🐁', '🐂', '🐃', '🐄', '🐅', '🐆', '🐇', '🐈', '⬛', '🐉', '🐊', '🐋', '🐌', '🐍', '🐎', '🐏', '🐐', '🐑', '🐒', '🐓', '🐔', '🐕', '🦺', '🐖', '🐗', '🐘', '🐙', '🐚', '🐛', '🐜', '🐝', '🐞', '🐟', '🐠', '🐡', '🐢', '🐣', '🐤', '🐥', '🐦', '🐧', '🐨', '🐩', '🐪', '🐫', '🐬', '🐭', '🐮', '🐯', '🐰', '🐱', '🐲', '🐳', '🐴', '🐵', '🐶', '🐷', '🐸', '🐹', '🐺', '🐻', '❄', '🐼', '🐽', '🐾', '🐿', '👀', '👁', '🗨', '👂', '👃', '👄', '👅', '👆', '👇', '👈', '👉', '👊', '👋', '👌', '👍', '👎', '👏', '👐', '👑', '👒', '👓', '👔', '👕', '👖', '👗', '👘', '👙', '👚', '👛', '👜', '👝', '👞', '👟', '👠', '👡', '👢', '👣', '👤', '👥', '👦', '👧', '👨', '💻', '💼', '🔧', '🔬', '🚀', '🚒', '🤝', '🦯', '🦰', '🦱', '🦲', '🦳', '🦼', '🦽', '⚕', '⚖', '✈', '❤', '💋', '👩', '👪', '👫', '👬', '👭', '👮', '👯', '👰', '👱', '👲', '👳', '👴', '👵', '👶', '👷', '👸', '👹', '👺', '👻', '👼', '👽', '👾', '👿', '💀', '💁', '💂', '💃', '💄', '💅', '💆', '💇', '💈', '💉', '💊', '💌', '💍', '💎', '💏', '💐', '💑', '💒', '💓', '💔', '💕', '💖', '💗', '💘', '💙', '💚', '💛', '💜', '💝', '💞', '💟', '💠', '💡', '💢', '💣', '💤', '💥', '💦', '💧', '💨', '💩', '💪', '💫', '💬', '💭', '💮', '💯', '💰', '💱', '💲', '💳', '💴', '💵', '💶', '💷', '💸', '💹', '💺', '💽', '💾', '💿', '📀', '📁', '📂', '📃', '📄', '📅', '📆', '📇', '📈', '📉', '📊', '📋', '📌', '📍', '📎', '📏', '📐', '📑', '📒', '📓', '📔', '📕', '📖', '📗', '📘', '📙', '📚', '📛', '📜', '📝', '📞', '📟', '📠', '📡', '📢', '📣', '📤', '📥', '📦', '📧', '📨', '📩', '📪', '📫', '📬', '📭', '📮', '📯', '📰', '📱', '📲', '📳', '📴', '📵', '📶', '📷', '📸', '📹', '📺', '📻', '📼', '📽', '📿', '🔀', '🔁', '🔂', '🔃', '🔄', '🔅', '🔆', '🔇', '🔈', '🔉', '🔊', '🔋', '🔌', '🔍', '🔎', '🔏', '🔐', '🔑', '🔒', '🔓', '🔔', '🔕', '🔖', '🔗', '🔘', '🔙', '🔚', '🔛', '🔜', '🔝', '🔞', '🔟', '🔠', '🔡', '🔢', '🔣', '🔤', '🔥', '🔦', '🔨', '🔩', '🔪', '🔫', '🔭', '🔮', '🔯', '🔰', '🔱', '🔲', '🔳', '🔴', '🔵', '🔶', '🔷', '🔸', '🔹', '🔺', '🔻', '🔼', '🔽', '🕉', '🕊', '🕋', '🕌', '🕍', '🕎', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚', '🕛', '🕜', '🕝', '🕞', '🕟', '🕠', '🕡', '🕢', '🕣', '🕤', '🕥', '🕦', '🕧', '🕯', '🕰', '🕳', '🕴', '🕵', '🕶', '🕷', '🕸', '🕹', '🕺', '🖇', '🖊', '🖋', '🖌', '🖍', '🖐', '🖕', '🖖', '🖤', '🖥', '🖨', '🖱', '🖲', '🖼', '🗂', '🗃', '🗄', '🗑', '🗒', '🗓', '🗜', '🗝', '🗞', '🗡', '🗣', '🗯', '🗳', '🗺', '🗻', '🗼', '🗽', '🗾', '🗿', '😀', '😁', '😂', '😃', '😄', '😅', '😆', '😇', '😈', '😉', '😊', '😋', '😌', '😍', '😎', '😏', '😐', '😑', '😒', '😓', '😔', '😕', '😖', '😗', '😘', '😙', '😚', '😛', '😜', '😝', '😞', '😟', '😠', '😡', '😢', '😣', '😤', '😥', '😦', '😧', '😨', '😩', '😪', '😫', '😬', '😭', '😮', '😯', '😰', '😱', '😲', '😳', '😴', '😵', '😶', '😷', '😸', '😹', '😺', '😻', '😼', '😽', '😾', '😿', '🙀', '🙁', '🙂', '🙃', '🙄', '🙅', '🙆', '🙇', '🙈', '🙉', '🙊', '🙋', '🙌', '🙍', '🙎', '🙏', '🚁', '🚂', '🚃', '🚄', '🚅', '🚆', '🚇', '🚈', '🚉', '🚊', '🚋', '🚌', '🚍', '🚎', '🚏', '🚐', '🚑', '🚓', '🚔', '🚕', '🚖', '🚗', '🚘', '🚙', '🚚', '🚛', '🚜', '🚝', '🚞', '🚟', '🚠', '🚡', '🚢', '🚣', '🚤', '🚥', '🚦', '🚧', '🚨', '🚩', '🚪', '🚫', '🚬', '🚭', '🚮', '🚯', '🚰', '🚱', '🚲', '🚳', '🚴', '🚵', '🚶', '🚷', '🚸', '🚹', '🚺', '🚻', '🚼', '🚽', '🚾', '🚿', '🛀', '🛁', '🛂', '🛃', '🛄', '🛅', '🛋', '🛌', '🛍', '🛎', '🛏', '🛐', '🛑', '🛒', '🛕', '🛖', '🛗', '🛜', '🛝', '🛞', '🛟', '🛠', '🛡', '🛢', '🛣', '🛤', '🛥', '🛩', '🛫', '🛬', '🛰', '🛳', '🛴', '🛵', '🛶', '🛷', '🛸', '🛹', '🛺', '🛻', '🛼', '🟠', '🟡', '🟢', '🟣', '🟤', '🟥', '🟦', '🟧', '🟨', '🟩', '🟪', '🟫', '🟰', '🤌', '🤍', '🤎', '🤏', '🤐', '🤑', '🤒', '🤓', '🤔', '🤕', '🤖', '🤗', '🤘', '🤙', '🤚', '🤛', '🤜', '🤞', '🤟', '🤠', '🤡', '🤢', '🤣', '🤤', '🤥', '🤦', '🤧', '🤨', '🤩', '🤪', '🤫', '🤬', '🤭', '🤮', '🤯', '🤰', '🤱', '🤲', '🤳', '🤴', '🤵', '🤶', '🤷', '🤸', '🤹', '🤺', '🤼', '🤽', '🤾', '🤿', '🥀', '🥁', '🥂', '🥃', '🥄', '🥅', '🥇', '🥈', '🥉', '🥊', '🥋', '🥌', '🥍', '🥎', '🥏', '🥐', '🥑', '🥒', '🥓', '🥔', '🥕', '🥖', '🥗', '🥘', '🥙', '🥚', '🥛', '🥜', '🥝', '🥞', '🥟', '🥠', '🥡', '🥢', '🥣', '🥤', '🥥', '🥦', '🥧', '🥨', '🥩', '🥪', '🥫', '🥬', '🥭', '🥮', '🥯', '🥰', '🥱', '🥲', '🥳', '🥴', '🥵', '🥶', '🥷', '🥸', '🥹', '🥺', '🥻', '🥼', '🥽', '🥾', '🥿', '🦀', '🦁', '🦂', '🦃', '🦄', '🦅', '🦆', '🦇', '🦈', '🦉', '🦊', '🦋', '🦌', '🦍', '🦎', '🦏', '🦐', '🦑', '🦒', '🦓', '🦔', '🦕', '🦖', '🦗', '🦘', '🦙', '🦚', '🦛', '🦜', '🦝', '🦞', '🦟', '🦠', '🦡', '🦢', '🦣', '🦤', '🦥', '🦦', '🦧', '🦨', '🦩', '🦪', '🦫', '🦬', '🦭', '🦮', '🦴', '🦵', '🦶', '🦷', '🦸', '🦹', '🦻', '🦾', '🦿', '🧀', '🧁', '🧂', '🧃', '🧄', '🧅', '🧆', '🧇', '🧈', '🧉', '🧊', '🧋', '🧌', '🧍', '🧎', '🧏', '🧐', '🧑', '🧒', '🧓', '🧔', '🧕', '🧖', '🧗', '🧘', '🧙', '🧚', '🧛', '🧜', '🧝', '🧞', '🧟', '🧠', '🧡', '🧢', '🧣', '🧤', '🧥', '🧦', '🧧', '🧨', '🧩', '🧪', '🧫', '🧬', '🧭', '🧮', '🧯', '🧰', '🧱', '🧲', '🧳', '🧴', '🧵', '🧶', '🧷', '🧸', '🧹', '🧺', '🧻', '🧼', '🧽', '🧾', '🧿', '🩰', '🩱', '🩲', '🩳', '🩴', '🩵', '🩶', '🩷', '🩸', '🩹', '🩺', '🩻', '🩼', '🪀', '🪁', '🪂', '🪃', '🪄', '🪅', '🪆', '🪇', '🪈', '🪐', '🪑', '🪒', '🪓', '🪔', '🪕', '🪖', '🪗', '🪘', '🪙', '🪚', '🪛', '🪜', '🪝', '🪞', '🪟', '🪠', '🪡', '🪢', '🪣', '🪤', '🪥', '🪦', '🪧', '🪨', '🪩', '🪪', '🪫', '🪬', '🪭', '🪮', '🪯', '🪰', '🪱', '🪲', '🪳', '🪴', '🪵', '🪶', '🪷', '🪸', '🪹', '🪺', '🪻', '🪼', '🪽', '🪿', '🫀', '🫁', '🫂', '🫃', '🫄', '🫅', '🫎', '🫏', '🫐', '🫑', '🫒', '🫓', '🫔', '🫕', '🫖', '🫗', '🫘', '🫙', '🫚', '🫛', '🫠', '🫡', '🫢', '🫣', '🫤', '🫥', '🫦', '🫧', '🫨', '🫰', '🫱', '🫲', '🫳', '🫴', '🫵', '🫶', '🫷', '🫸', '‼', '⁉', '™', 'ℹ', '↔', '↕', '↖', '↗', '↘', '↙', '↩', '↪', '⃣', '⌚', '⌛', '⌨', '⏏', '⏩', '⏪', '⏫', '⏬', '⏭', '⏮', '⏯', '⏰', '⏱', '⏲', '⏳', '⏸', '⏹', '⏺', 'Ⓜ', '▪', '▫', '▶', '◀', '◻', '◼', '◽', '◾', '☀', '☁', '☂', '☃', '☄', '☎', '☑', '☔', '☕', '☘', '☝', '☢', '☣', '☦', '☪', '☮', '☯', '☸', '☹', '☺', '♈', '♉', '♊', '♋', '♌', '♍', '♎', '♏', '♐', '♑', '♒', '♓', '♟', '♠', '♣', '♥', '♦', '♨', '♻', '♾', '♿', '⚒', '⚓', '⚔', '⚗', '⚙', '⚛', '⚜', '⚠', '⚡', '⚪', '⚫', '⚰', '⚱', '⚽', '⚾', '⛄', '⛅', '⛈', '⛎', '⛏', '⛑', '⛓', '⛔', '⛩', '⛪', '⛰', '⛱', '⛲', '⛳', '⛴', '⛵', '⛷', '⛸', '⛹', '⛺', '⛽', '✂', '✅', '✉', '✊', '✋', '✌', '✍', '✏', '✒', '✔', '✖', '✝', '✡', '✨', '✳', '✴', '❇', '❌', '❎', '❓', '❔', '❕', '❗', '❣', '➕', '➖', '➗', '➡', '➰', '➿', '⤴', '⤵', '⬅', '⬆', '⬇', '⬜', '⭐', '⭕', '〰', '〽', '㊗', '㊙', '' ); // END: emoji arrays if ( 'entities' === $type ) { return $entities; } return $partials; } /** * Shortens a URL, to be used as link text. * * @since 1.2.0 * @since 4.4.0 Moved to wp-includes/formatting.php from wp-admin/includes/misc.php and added $length param. * * @param string $url URL to shorten. * @param int $length Optional. Maximum length of the shortened URL. Default 35 characters. * @return string Shortened URL. */ function url_shorten( $url, $length = 35 ) { $stripped = str_replace( array( 'https://', 'http://', 'www.' ), '', $url ); $short_url = untrailingslashit( $stripped ); if ( strlen( $short_url ) > $length ) { $short_url = substr( $short_url, 0, $length - 3 ) . '…'; } return $short_url; } /** * Sanitizes a hex color. * * Returns either '', a 3 or 6 digit hex color (with #), or nothing. * For sanitizing values without a #, see sanitize_hex_color_no_hash(). * * @since 3.4.0 * * @param string $color * @return string|void */ function sanitize_hex_color( $color ) { if ( '' === $color ) { return ''; } // 3 or 6 hex digits, or the empty string. if ( preg_match( '|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) ) { return $color; } } /** * Sanitizes a hex color without a hash. Use sanitize_hex_color() when possible. * * Saving hex colors without a hash puts the burden of adding the hash on the * UI, which makes it difficult to use or upgrade to other color types such as * rgba, hsl, rgb, and HTML color names. * * Returns either '', a 3 or 6 digit hex color (without a #), or null. * * @since 3.4.0 * * @param string $color * @return string|null */ function sanitize_hex_color_no_hash( $color ) { $color = ltrim( $color, '#' ); if ( '' === $color ) { return ''; } return sanitize_hex_color( '#' . $color ) ? $color : null; } /** * Ensures that any hex color is properly hashed. * Otherwise, returns value untouched. * * This method should only be necessary if using sanitize_hex_color_no_hash(). * * @since 3.4.0 * * @param string $color * @return string */ function maybe_hash_hex_color( $color ) { $unhashed = sanitize_hex_color_no_hash( $color ); if ( $unhashed ) { return '#' . $unhashed; } return $color; } /** * Option API * * @package WordPress * @subpackage Option */ /** * Retrieves an option value based on an option name. * * If the option does not exist, and a default value is not provided, * boolean false is returned. This could be used to check whether you need * to initialize an option during installation of a plugin, however that * can be done better by using add_option() which will not overwrite * existing options. * * Not initializing an option and using boolean `false` as a return value * is a bad practice as it triggers an additional database query. * * The type of the returned value can be different from the type that was passed * when saving or updating the option. If the option value was serialized, * then it will be unserialized when it is returned. In this case the type will * be the same. For example, storing a non-scalar value like an array will * return the same array. * * In most cases non-string scalar and null values will be converted and returned * as string equivalents. * * Exceptions: * * 1. When the option has not been saved in the database, the `$default_value` value * is returned if provided. If not, boolean `false` is returned. * 2. When one of the Options API filters is used: {@see 'pre_option_$option'}, * {@see 'default_option_$option'}, or {@see 'option_$option'}, the returned * value may not match the expected type. * 3. When the option has just been saved in the database, and get_option() * is used right after, non-string scalar and null values are not converted to * string equivalents and the original type is returned. * * Examples: * * When adding options like this: `add_option( 'my_option_name', 'value' )` * and then retrieving them with `get_option( 'my_option_name' )`, the returned * values will be: * * - `false` returns `string(0) ""` * - `true` returns `string(1) "1"` * - `0` returns `string(1) "0"` * - `1` returns `string(1) "1"` * - `'0'` returns `string(1) "0"` * - `'1'` returns `string(1) "1"` * - `null` returns `string(0) ""` * * When adding options with non-scalar values like * `add_option( 'my_array', array( false, 'str', null ) )`, the returned value * will be identical to the original as it is serialized before saving * it in the database: * * array(3) { * [0] => bool(false) * [1] => string(3) "str" * [2] => NULL * } * * @since 1.5.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $option Name of the option to retrieve. Expected to not be SQL-escaped. * @param mixed $default_value Optional. Default value to return if the option does not exist. * @return mixed Value of the option. A value of any type may be returned, including * scalar (string, boolean, float, integer), null, array, object. * Scalar and null values will be returned as strings as long as they originate * from a database stored option value. If there is no option in the database, * boolean `false` is returned. */ function get_option( $option, $default_value = false ) { global $wpdb; if ( is_scalar( $option ) ) { $option = trim( $option ); } if ( empty( $option ) ) { return false; } /* * Until a proper _deprecated_option() function can be introduced, * redirect requests to deprecated keys to the new, correct ones. */ $deprecated_keys = array( 'blacklist_keys' => 'disallowed_keys', 'comment_whitelist' => 'comment_previously_approved', ); if ( isset( $deprecated_keys[ $option ] ) && ! wp_installing() ) { _deprecated_argument( __FUNCTION__, '5.5.0', sprintf( /* translators: 1: Deprecated option key, 2: New option key. */ __( 'The "%1$s" option key has been renamed to "%2$s".' ), $option, $deprecated_keys[ $option ] ) ); return get_option( $deprecated_keys[ $option ], $default_value ); } /** * Filters the value of an existing option before it is retrieved. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * Returning a value other than false from the filter will short-circuit retrieval * and return that value instead. * * @since 1.5.0 * @since 4.4.0 The `$option` parameter was added. * @since 4.9.0 The `$default_value` parameter was added. * * @param mixed $pre_option The value to return instead of the option value. This differs from * `$default_value`, which is used as the fallback value in the event * the option doesn't exist elsewhere in get_option(). * Default false (to skip past the short-circuit). * @param string $option Option name. * @param mixed $default_value The fallback value to return if the option does not exist. * Default false. */ $pre = apply_filters( "pre_option_{$option}", false, $option, $default_value ); /** * Filters the value of all existing options before it is retrieved. * * Returning a truthy value from the filter will effectively short-circuit retrieval * and return the passed value instead. * * @since 6.1.0 * * @param mixed $pre_option The value to return instead of the option value. This differs from * `$default_value`, which is used as the fallback value in the event * the option doesn't exist elsewhere in get_option(). * Default false (to skip past the short-circuit). * @param string $option Name of the option. * @param mixed $default_value The fallback value to return if the option does not exist. * Default false. */ $pre = apply_filters( 'pre_option', $pre, $option, $default_value ); if ( false !== $pre ) { return $pre; } if ( defined( 'WP_SETUP_CONFIG' ) ) { return false; } // Distinguish between `false` as a default, and not passing one. $passed_default = func_num_args() > 1; if ( ! wp_installing() ) { $alloptions = wp_load_alloptions(); if ( isset( $alloptions[ $option ] ) ) { $value = $alloptions[ $option ]; } else { $value = wp_cache_get( $option, 'options' ); if ( false === $value ) { // Prevent non-existent options from triggering multiple queries. $notoptions = wp_cache_get( 'notoptions', 'options' ); // Prevent non-existent `notoptions` key from triggering multiple key lookups. if ( ! is_array( $notoptions ) ) { $notoptions = array(); wp_cache_set( 'notoptions', $notoptions, 'options' ); } elseif ( isset( $notoptions[ $option ] ) ) { /** * Filters the default value for an option. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * @since 3.4.0 * @since 4.4.0 The `$option` parameter was added. * @since 4.7.0 The `$passed_default` parameter was added to distinguish between a `false` value and the default parameter value. * * @param mixed $default_value The default value to return if the option does not exist * in the database. * @param string $option Option name. * @param bool $passed_default Was `get_option()` passed a default value? */ return apply_filters( "default_option_{$option}", $default_value, $option, $passed_default ); } $row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $option ) ); // Has to be get_row() instead of get_var() because of funkiness with 0, false, null values. if ( is_object( $row ) ) { $value = $row->option_value; wp_cache_add( $option, $value, 'options' ); } else { // Option does not exist, so we must cache its non-existence. $notoptions[ $option ] = true; wp_cache_set( 'notoptions', $notoptions, 'options' ); /** This filter is documented in wp-includes/option.php */ return apply_filters( "default_option_{$option}", $default_value, $option, $passed_default ); } } } } else { $suppress = $wpdb->suppress_errors(); $row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $option ) ); $wpdb->suppress_errors( $suppress ); if ( is_object( $row ) ) { $value = $row->option_value; } else { /** This filter is documented in wp-includes/option.php */ return apply_filters( "default_option_{$option}", $default_value, $option, $passed_default ); } } // If home is not set, use siteurl. if ( 'home' === $option && '' === $value ) { return get_option( 'siteurl' ); } if ( in_array( $option, array( 'siteurl', 'home', 'category_base', 'tag_base' ), true ) ) { $value = untrailingslashit( $value ); } /** * Filters the value of an existing option. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * @since 1.5.0 As 'option_' . $setting * @since 3.0.0 * @since 4.4.0 The `$option` parameter was added. * * @param mixed $value Value of the option. If stored serialized, it will be * unserialized prior to being returned. * @param string $option Option name. */ return apply_filters( "option_{$option}", maybe_unserialize( $value ), $option ); } /** * Primes specific options into the cache with a single database query. * * Only options that do not already exist in cache will be loaded. * * @since 6.4.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string[] $options An array of option names to be loaded. */ function wp_prime_option_caches( $options ) { global $wpdb; $alloptions = wp_load_alloptions(); $cached_options = wp_cache_get_multiple( $options, 'options' ); $notoptions = wp_cache_get( 'notoptions', 'options' ); if ( ! is_array( $notoptions ) ) { $notoptions = array(); } // Filter options that are not in the cache. $options_to_prime = array(); foreach ( $options as $option ) { if ( ( ! isset( $cached_options[ $option ] ) || false === $cached_options[ $option ] ) && ! isset( $alloptions[ $option ] ) && ! isset( $notoptions[ $option ] ) ) { $options_to_prime[] = $option; } } // Bail early if there are no options to be loaded. if ( empty( $options_to_prime ) ) { return; } $results = $wpdb->get_results( $wpdb->prepare( sprintf( "SELECT option_name, option_value FROM $wpdb->options WHERE option_name IN (%s)", implode( ',', array_fill( 0, count( $options_to_prime ), '%s' ) ) ), $options_to_prime ) ); $options_found = array(); foreach ( $results as $result ) { /* * The cache is primed with the raw value (i.e. not maybe_unserialized). * * `get_option()` will handle unserializing the value as needed. */ $options_found[ $result->option_name ] = $result->option_value; } wp_cache_set_multiple( $options_found, 'options' ); // If all options were found, no need to update `notoptions` cache. if ( count( $options_found ) === count( $options_to_prime ) ) { return; } $options_not_found = array_diff( $options_to_prime, array_keys( $options_found ) ); // Add the options that were not found to the cache. $update_notoptions = false; foreach ( $options_not_found as $option_name ) { if ( ! isset( $notoptions[ $option_name ] ) ) { $notoptions[ $option_name ] = true; $update_notoptions = true; } } // Only update the cache if it was modified. if ( $update_notoptions ) { wp_cache_set( 'notoptions', $notoptions, 'options' ); } } /** * Primes the cache of all options registered with a specific option group. * * @since 6.4.0 * * @global array $new_allowed_options * * @param string $option_group The option group to load options for. */ function wp_prime_option_caches_by_group( $option_group ) { global $new_allowed_options; if ( isset( $new_allowed_options[ $option_group ] ) ) { wp_prime_option_caches( $new_allowed_options[ $option_group ] ); } } /** * Retrieves multiple options. * * Options are loaded as necessary first in order to use a single database query at most. * * @since 6.4.0 * * @param string[] $options An array of option names to retrieve. * @return array An array of key-value pairs for the requested options. */ function get_options( $options ) { wp_prime_option_caches( $options ); $result = array(); foreach ( $options as $option ) { $result[ $option ] = get_option( $option ); } return $result; } /** * Sets the autoload values for multiple options in the database. * * Autoloading too many options can lead to performance problems, especially if the options are not frequently used. * This function allows modifying the autoload value for multiple options without changing the actual option value. * This is for example recommended for plugin activation and deactivation hooks, to ensure any options exclusively used * by the plugin which are generally autoloaded can be set to not autoload when the plugin is inactive. * * @since 6.4.0 * @since 6.7.0 The autoload values 'yes' and 'no' are deprecated. * * @global wpdb $wpdb WordPress database abstraction object. * * @param array $options Associative array of option names and their autoload values to set. The option names are * expected to not be SQL-escaped. The autoload values should be boolean values. For backward * compatibility 'yes' and 'no' are also accepted, though using these values is deprecated. * @return array Associative array of all provided $options as keys and boolean values for whether their autoload value * was updated. */ function wp_set_option_autoload_values( array $options ) { global $wpdb; if ( ! $options ) { return array(); } $grouped_options = array( 'on' => array(), 'off' => array(), ); $results = array(); foreach ( $options as $option => $autoload ) { wp_protect_special_option( $option ); // Ensure only valid options can be passed. /* * Sanitize autoload value and categorize accordingly. * The values 'yes', 'no', 'on', and 'off' are supported for backward compatibility. */ if ( 'off' === $autoload || 'no' === $autoload || false === $autoload ) { $grouped_options['off'][] = $option; } else { $grouped_options['on'][] = $option; } $results[ $option ] = false; // Initialize result value. } $where = array(); $where_args = array(); foreach ( $grouped_options as $autoload => $options ) { if ( ! $options ) { continue; } $placeholders = implode( ',', array_fill( 0, count( $options ), '%s' ) ); $where[] = "autoload != '%s' AND option_name IN ($placeholders)"; $where_args[] = $autoload; foreach ( $options as $option ) { $where_args[] = $option; } } $where = 'WHERE ' . implode( ' OR ', $where ); /* * Determine the relevant options that do not already use the given autoload value. * If no options are returned, no need to update. */ // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare $options_to_update = $wpdb->get_col( $wpdb->prepare( "SELECT option_name FROM $wpdb->options $where", $where_args ) ); if ( ! $options_to_update ) { return $results; } // Run UPDATE queries as needed (maximum 2) to update the relevant options' autoload values to 'yes' or 'no'. foreach ( $grouped_options as $autoload => $options ) { if ( ! $options ) { continue; } $options = array_intersect( $options, $options_to_update ); $grouped_options[ $autoload ] = $options; if ( ! $grouped_options[ $autoload ] ) { continue; } // Run query to update autoload value for all the options where it is needed. $success = $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->options SET autoload = %s WHERE option_name IN (" . implode( ',', array_fill( 0, count( $grouped_options[ $autoload ] ), '%s' ) ) . ')', array_merge( array( $autoload ), $grouped_options[ $autoload ] ) ) ); if ( ! $success ) { // Set option list to an empty array to indicate no options were updated. $grouped_options[ $autoload ] = array(); continue; } // Assume that on success all options were updated, which should be the case given only new values are sent. foreach ( $grouped_options[ $autoload ] as $option ) { $results[ $option ] = true; } } /* * If any options were changed to 'on', delete their individual caches, and delete 'alloptions' cache so that it * is refreshed as needed. * If no options were changed to 'on' but any options were changed to 'no', delete them from the 'alloptions' * cache. This is not necessary when options were changed to 'on', since in that situation the entire cache is * deleted anyway. */ if ( $grouped_options['on'] ) { wp_cache_delete_multiple( $grouped_options['on'], 'options' ); wp_cache_delete( 'alloptions', 'options' ); } elseif ( $grouped_options['off'] ) { $alloptions = wp_load_alloptions( true ); foreach ( $grouped_options['off'] as $option ) { if ( isset( $alloptions[ $option ] ) ) { unset( $alloptions[ $option ] ); } } wp_cache_set( 'alloptions', $alloptions, 'options' ); } return $results; } /** * Sets the autoload value for multiple options in the database. * * This is a wrapper for {@see wp_set_option_autoload_values()}, which can be used to set different autoload values for * each option at once. * * @since 6.4.0 * @since 6.7.0 The autoload values 'yes' and 'no' are deprecated. * * @see wp_set_option_autoload_values() * * @param string[] $options List of option names. Expected to not be SQL-escaped. * @param bool $autoload Autoload value to control whether to load the options when WordPress starts up. * For backward compatibility 'yes' and 'no' are also accepted, though using these values is * deprecated. * @return array Associative array of all provided $options as keys and boolean values for whether their autoload value * was updated. */ function wp_set_options_autoload( array $options, $autoload ) { return wp_set_option_autoload_values( array_fill_keys( $options, $autoload ) ); } /** * Sets the autoload value for an option in the database. * * This is a wrapper for {@see wp_set_option_autoload_values()}, which can be used to set the autoload value for * multiple options at once. * * @since 6.4.0 * @since 6.7.0 The autoload values 'yes' and 'no' are deprecated. * * @see wp_set_option_autoload_values() * * @param string $option Name of the option. Expected to not be SQL-escaped. * @param bool $autoload Autoload value to control whether to load the option when WordPress starts up. * For backward compatibility 'yes' and 'no' are also accepted, though using these values is * deprecated. * @return bool True if the autoload value was modified, false otherwise. */ function wp_set_option_autoload( $option, $autoload ) { $result = wp_set_option_autoload_values( array( $option => $autoload ) ); if ( isset( $result[ $option ] ) ) { return $result[ $option ]; } return false; } /** * Protects WordPress special option from being modified. * * Will die if $option is in protected list. Protected options are 'alloptions' * and 'notoptions' options. * * @since 2.2.0 * * @param string $option Option name. */ function wp_protect_special_option( $option ) { if ( 'alloptions' === $option || 'notoptions' === $option ) { wp_die( sprintf( /* translators: %s: Option name. */ __( '%s is a protected WP option and may not be modified' ), esc_html( $option ) ) ); } } /** * Prints option value after sanitizing for forms. * * @since 1.5.0 * * @param string $option Option name. */ function form_option( $option ) { echo esc_attr( get_option( $option ) ); } /** * Loads and caches all autoloaded options, if available or all options. * * @since 2.2.0 * @since 5.3.1 The `$force_cache` parameter was added. * * @global wpdb $wpdb WordPress database abstraction object. * * @param bool $force_cache Optional. Whether to force an update of the local cache * from the persistent cache. Default false. * @return array List of all options. */ function wp_load_alloptions( $force_cache = false ) { global $wpdb; /** * Filters the array of alloptions before it is populated. * * Returning an array from the filter will effectively short circuit * wp_load_alloptions(), returning that value instead. * * @since 6.2.0 * * @param array|null $alloptions An array of alloptions. Default null. * @param bool $force_cache Whether to force an update of the local cache from the persistent cache. Default false. */ $alloptions = apply_filters( 'pre_wp_load_alloptions', null, $force_cache ); if ( is_array( $alloptions ) ) { return $alloptions; } if ( ! wp_installing() || ! is_multisite() ) { $alloptions = wp_cache_get( 'alloptions', 'options', $force_cache ); } else { $alloptions = false; } if ( ! $alloptions ) { $suppress = $wpdb->suppress_errors(); $alloptions_db = $wpdb->get_results( "SELECT option_name, option_value FROM $wpdb->options WHERE autoload IN ( '" . implode( "', '", esc_sql( wp_autoload_values_to_autoload() ) ) . "' )" ); if ( ! $alloptions_db ) { $alloptions_db = $wpdb->get_results( "SELECT option_name, option_value FROM $wpdb->options" ); } $wpdb->suppress_errors( $suppress ); $alloptions = array(); foreach ( (array) $alloptions_db as $o ) { $alloptions[ $o->option_name ] = $o->option_value; } if ( ! wp_installing() || ! is_multisite() ) { /** * Filters all options before caching them. * * @since 4.9.0 * * @param array $alloptions Array with all options. */ $alloptions = apply_filters( 'pre_cache_alloptions', $alloptions ); wp_cache_add( 'alloptions', $alloptions, 'options' ); } } /** * Filters all options after retrieving them. * * @since 4.9.0 * * @param array $alloptions Array with all options. */ return apply_filters( 'alloptions', $alloptions ); } /** * Primes specific network options for the current network into the cache with a single database query. * * Only network options that do not already exist in cache will be loaded. * * If site is not multisite, then call wp_prime_option_caches(). * * @since 6.6.0 * * @see wp_prime_network_option_caches() * * @param string[] $options An array of option names to be loaded. */ function wp_prime_site_option_caches( array $options ) { wp_prime_network_option_caches( null, $options ); } /** * Primes specific network options into the cache with a single database query. * * Only network options that do not already exist in cache will be loaded. * * If site is not multisite, then call wp_prime_option_caches(). * * @since 6.6.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $network_id ID of the network. Can be null to default to the current network ID. * @param string[] $options An array of option names to be loaded. */ function wp_prime_network_option_caches( $network_id, array $options ) { global $wpdb; if ( wp_installing() ) { return; } if ( ! is_multisite() ) { wp_prime_option_caches( $options ); return; } if ( $network_id && ! is_numeric( $network_id ) ) { return; } $network_id = (int) $network_id; // Fallback to the current network if a network ID is not specified. if ( ! $network_id ) { $network_id = get_current_network_id(); } $cache_keys = array(); foreach ( $options as $option ) { $cache_keys[ $option ] = "{$network_id}:{$option}"; } $cache_group = 'site-options'; $cached_options = wp_cache_get_multiple( array_values( $cache_keys ), $cache_group ); $notoptions_key = "$network_id:notoptions"; $notoptions = wp_cache_get( $notoptions_key, $cache_group ); if ( ! is_array( $notoptions ) ) { $notoptions = array(); } // Filter options that are not in the cache. $options_to_prime = array(); foreach ( $cache_keys as $option => $cache_key ) { if ( ( ! isset( $cached_options[ $cache_key ] ) || false === $cached_options[ $cache_key ] ) && ! isset( $notoptions[ $option ] ) ) { $options_to_prime[] = $option; } } // Bail early if there are no options to be loaded. if ( empty( $options_to_prime ) ) { return; } $query_args = $options_to_prime; $query_args[] = $network_id; $results = $wpdb->get_results( $wpdb->prepare( sprintf( "SELECT meta_key, meta_value FROM $wpdb->sitemeta WHERE meta_key IN (%s) AND site_id = %s", implode( ',', array_fill( 0, count( $options_to_prime ), '%s' ) ), '%d' ), $query_args ) ); $data = array(); $options_found = array(); foreach ( $results as $result ) { $key = $result->meta_key; $cache_key = $cache_keys[ $key ]; $data[ $cache_key ] = maybe_unserialize( $result->meta_value ); $options_found[] = $key; } wp_cache_set_multiple( $data, $cache_group ); // If all options were found, no need to update `notoptions` cache. if ( count( $options_found ) === count( $options_to_prime ) ) { return; } $options_not_found = array_diff( $options_to_prime, $options_found ); // Add the options that were not found to the cache. $update_notoptions = false; foreach ( $options_not_found as $option_name ) { if ( ! isset( $notoptions[ $option_name ] ) ) { $notoptions[ $option_name ] = true; $update_notoptions = true; } } // Only update the cache if it was modified. if ( $update_notoptions ) { wp_cache_set( $notoptions_key, $notoptions, $cache_group ); } } /** * Loads and primes caches of certain often requested network options if is_multisite(). * * @since 3.0.0 * @since 6.3.0 Also prime caches for network options when persistent object cache is enabled. * @since 6.6.0 Uses wp_prime_network_option_caches(). * * @param int $network_id Optional. Network ID of network for which to prime network options cache. Defaults to current network. */ function wp_load_core_site_options( $network_id = null ) { if ( ! is_multisite() || wp_installing() ) { return; } $core_options = array( 'site_name', 'siteurl', 'active_sitewide_plugins', '_site_transient_timeout_theme_roots', '_site_transient_theme_roots', 'site_admins', 'can_compress_scripts', 'global_terms_enabled', 'ms_files_rewriting', 'WPLANG' ); wp_prime_network_option_caches( $network_id, $core_options ); } /** * Updates the value of an option that was already added. * * You do not need to serialize values. If the value needs to be serialized, * then it will be serialized before it is inserted into the database. * Remember, resources cannot be serialized or added as an option. * * If the option does not exist, it will be created. * This function is designed to work with or without a logged-in user. In terms of security, * plugin developers should check the current user's capabilities before updating any options. * * @since 1.0.0 * @since 4.2.0 The `$autoload` parameter was added. * @since 6.7.0 The autoload values 'yes' and 'no' are deprecated. * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $option Name of the option to update. Expected to not be SQL-escaped. * @param mixed $value Option value. Must be serializable if non-scalar. Expected to not be SQL-escaped. * @param bool|null $autoload Optional. Whether to load the option when WordPress starts up. * Accepts a boolean, or `null` to stick with the initial value or, if no initial value is * set, to leave the decision up to default heuristics in WordPress. * For existing options, `$autoload` can only be updated using `update_option()` if `$value` * is also changed. * For backward compatibility 'yes' and 'no' are also accepted, though using these values is * deprecated. * Autoloading too many options can lead to performance problems, especially if the * options are not frequently used. For options which are accessed across several places * in the frontend, it is recommended to autoload them, by using true. * For options which are accessed only on few specific URLs, it is recommended * to not autoload them, by using false. * For non-existent options, the default is null, which means WordPress will determine * the autoload value. * @return bool True if the value was updated, false otherwise. */ function update_option( $option, $value, $autoload = null ) { global $wpdb; if ( is_scalar( $option ) ) { $option = trim( $option ); } if ( empty( $option ) ) { return false; } /* * Until a proper _deprecated_option() function can be introduced, * redirect requests to deprecated keys to the new, correct ones. */ $deprecated_keys = array( 'blacklist_keys' => 'disallowed_keys', 'comment_whitelist' => 'comment_previously_approved', ); if ( isset( $deprecated_keys[ $option ] ) && ! wp_installing() ) { _deprecated_argument( __FUNCTION__, '5.5.0', sprintf( /* translators: 1: Deprecated option key, 2: New option key. */ __( 'The "%1$s" option key has been renamed to "%2$s".' ), $option, $deprecated_keys[ $option ] ) ); return update_option( $deprecated_keys[ $option ], $value, $autoload ); } wp_protect_special_option( $option ); if ( is_object( $value ) ) { $value = clone $value; } $value = sanitize_option( $option, $value ); $old_value = get_option( $option ); /** * Filters a specific option before its value is (maybe) serialized and updated. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * @since 2.6.0 * @since 4.4.0 The `$option` parameter was added. * * @param mixed $value The new, unserialized option value. * @param mixed $old_value The old option value. * @param string $option Option name. */ $value = apply_filters( "pre_update_option_{$option}", $value, $old_value, $option ); /** * Filters an option before its value is (maybe) serialized and updated. * * @since 3.9.0 * * @param mixed $value The new, unserialized option value. * @param string $option Name of the option. * @param mixed $old_value The old option value. */ $value = apply_filters( 'pre_update_option', $value, $option, $old_value ); /* * If the new and old values are the same, no need to update. * * Unserialized values will be adequate in most cases. If the unserialized * data differs, the (maybe) serialized data is checked to avoid * unnecessary database calls for otherwise identical object instances. * * See https://core.trac.wordpress.org/ticket/38903 */ if ( $value === $old_value || maybe_serialize( $value ) === maybe_serialize( $old_value ) ) { return false; } /** This filter is documented in wp-includes/option.php */ if ( apply_filters( "default_option_{$option}", false, $option, false ) === $old_value ) { return add_option( $option, $value, '', $autoload ); } $serialized_value = maybe_serialize( $value ); /** * Fires immediately before an option value is updated. * * @since 2.9.0 * * @param string $option Name of the option to update. * @param mixed $old_value The old option value. * @param mixed $value The new option value. */ do_action( 'update_option', $option, $old_value, $value ); $update_args = array( 'option_value' => $serialized_value, ); if ( null !== $autoload ) { $update_args['autoload'] = wp_determine_option_autoload_value( $option, $value, $serialized_value, $autoload ); } else { // Retrieve the current autoload value to reevaluate it in case it was set automatically. $raw_autoload = $wpdb->get_var( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name = %s LIMIT 1", $option ) ); $allow_values = array( 'auto-on', 'auto-off', 'auto' ); if ( in_array( $raw_autoload, $allow_values, true ) ) { $autoload = wp_determine_option_autoload_value( $option, $value, $serialized_value, $autoload ); if ( $autoload !== $raw_autoload ) { $update_args['autoload'] = $autoload; } } } $result = $wpdb->update( $wpdb->options, $update_args, array( 'option_name' => $option ) ); if ( ! $result ) { return false; } $notoptions = wp_cache_get( 'notoptions', 'options' ); if ( is_array( $notoptions ) && isset( $notoptions[ $option ] ) ) { unset( $notoptions[ $option ] ); wp_cache_set( 'notoptions', $notoptions, 'options' ); } if ( ! wp_installing() ) { if ( ! isset( $update_args['autoload'] ) ) { // Update the cached value based on where it is currently cached. $alloptions = wp_load_alloptions( true ); if ( isset( $alloptions[ $option ] ) ) { $alloptions[ $option ] = $serialized_value; wp_cache_set( 'alloptions', $alloptions, 'options' ); } else { wp_cache_set( $option, $serialized_value, 'options' ); } } elseif ( in_array( $update_args['autoload'], wp_autoload_values_to_autoload(), true ) ) { // Delete the individual cache, then set in alloptions cache. wp_cache_delete( $option, 'options' ); $alloptions = wp_load_alloptions( true ); $alloptions[ $option ] = $serialized_value; wp_cache_set( 'alloptions', $alloptions, 'options' ); } else { // Delete the alloptions cache, then set the individual cache. $alloptions = wp_load_alloptions( true ); if ( isset( $alloptions[ $option ] ) ) { unset( $alloptions[ $option ] ); wp_cache_set( 'alloptions', $alloptions, 'options' ); } wp_cache_set( $option, $serialized_value, 'options' ); } } /** * Fires after the value of a specific option has been successfully updated. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * @since 2.0.1 * @since 4.4.0 The `$option` parameter was added. * * @param mixed $old_value The old option value. * @param mixed $value The new option value. * @param string $option Option name. */ do_action( "update_option_{$option}", $old_value, $value, $option ); /** * Fires after the value of an option has been successfully updated. * * @since 2.9.0 * * @param string $option Name of the updated option. * @param mixed $old_value The old option value. * @param mixed $value The new option value. */ do_action( 'updated_option', $option, $old_value, $value ); return true; } /** * Adds a new option. * * You do not need to serialize values. If the value needs to be serialized, * then it will be serialized before it is inserted into the database. * Remember, resources cannot be serialized or added as an option. * * You can create options without values and then update the values later. * Existing options will not be updated and checks are performed to ensure that you * aren't adding a protected WordPress option. Care should be taken to not name * options the same as the ones which are protected. * * @since 1.0.0 * @since 6.6.0 The $autoload parameter's default value was changed to null. * @since 6.7.0 The autoload values 'yes' and 'no' are deprecated. * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $option Name of the option to add. Expected to not be SQL-escaped. * @param mixed $value Optional. Option value. Must be serializable if non-scalar. * Expected to not be SQL-escaped. * @param string $deprecated Optional. Description. Not used anymore. * @param bool|null $autoload Optional. Whether to load the option when WordPress starts up. * Accepts a boolean, or `null` to leave the decision up to default heuristics in * WordPress. For backward compatibility 'yes' and 'no' are also accepted, though using * these values is deprecated. * Autoloading too many options can lead to performance problems, especially if the * options are not frequently used. For options which are accessed across several places * in the frontend, it is recommended to autoload them, by using true. * For options which are accessed only on few specific URLs, it is recommended * to not autoload them, by using false. * Default is null, which means WordPress will determine the autoload value. * @return bool True if the option was added, false otherwise. */ function add_option( $option, $value = '', $deprecated = '', $autoload = null ) { global $wpdb; if ( ! empty( $deprecated ) ) { _deprecated_argument( __FUNCTION__, '2.3.0' ); } if ( is_scalar( $option ) ) { $option = trim( $option ); } if ( empty( $option ) ) { return false; } /* * Until a proper _deprecated_option() function can be introduced, * redirect requests to deprecated keys to the new, correct ones. */ $deprecated_keys = array( 'blacklist_keys' => 'disallowed_keys', 'comment_whitelist' => 'comment_previously_approved', ); if ( isset( $deprecated_keys[ $option ] ) && ! wp_installing() ) { _deprecated_argument( __FUNCTION__, '5.5.0', sprintf( /* translators: 1: Deprecated option key, 2: New option key. */ __( 'The "%1$s" option key has been renamed to "%2$s".' ), $option, $deprecated_keys[ $option ] ) ); return add_option( $deprecated_keys[ $option ], $value, $deprecated, $autoload ); } wp_protect_special_option( $option ); if ( is_object( $value ) ) { $value = clone $value; } $value = sanitize_option( $option, $value ); /* * Make sure the option doesn't already exist. * We can check the 'notoptions' cache before we ask for a DB query. */ $notoptions = wp_cache_get( 'notoptions', 'options' ); if ( ! is_array( $notoptions ) || ! isset( $notoptions[ $option ] ) ) { /** This filter is documented in wp-includes/option.php */ if ( apply_filters( "default_option_{$option}", false, $option, false ) !== get_option( $option ) ) { return false; } } $serialized_value = maybe_serialize( $value ); $autoload = wp_determine_option_autoload_value( $option, $value, $serialized_value, $autoload ); /** * Fires before an option is added. * * @since 2.9.0 * * @param string $option Name of the option to add. * @param mixed $value Value of the option. */ do_action( 'add_option', $option, $value ); $result = $wpdb->query( $wpdb->prepare( "INSERT INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE `option_name` = VALUES(`option_name`), `option_value` = VALUES(`option_value`), `autoload` = VALUES(`autoload`)", $option, $serialized_value, $autoload ) ); if ( ! $result ) { return false; } if ( ! wp_installing() ) { if ( in_array( $autoload, wp_autoload_values_to_autoload(), true ) ) { $alloptions = wp_load_alloptions( true ); $alloptions[ $option ] = $serialized_value; wp_cache_set( 'alloptions', $alloptions, 'options' ); } else { wp_cache_set( $option, $serialized_value, 'options' ); } } // This option exists now. $notoptions = wp_cache_get( 'notoptions', 'options' ); // Yes, again... we need it to be fresh. if ( is_array( $notoptions ) && isset( $notoptions[ $option ] ) ) { unset( $notoptions[ $option ] ); wp_cache_set( 'notoptions', $notoptions, 'options' ); } /** * Fires after a specific option has been added. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * @since 2.5.0 As "add_option_{$name}" * @since 3.0.0 * * @param string $option Name of the option to add. * @param mixed $value Value of the option. */ do_action( "add_option_{$option}", $option, $value ); /** * Fires after an option has been added. * * @since 2.9.0 * * @param string $option Name of the added option. * @param mixed $value Value of the option. */ do_action( 'added_option', $option, $value ); return true; } /** * Removes an option by name. Prevents removal of protected WordPress options. * * @since 1.2.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $option Name of the option to delete. Expected to not be SQL-escaped. * @return bool True if the option was deleted, false otherwise. */ function delete_option( $option ) { global $wpdb; if ( is_scalar( $option ) ) { $option = trim( $option ); } if ( empty( $option ) ) { return false; } wp_protect_special_option( $option ); // Get the ID, if no ID then return. $row = $wpdb->get_row( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name = %s", $option ) ); if ( is_null( $row ) ) { return false; } /** * Fires immediately before an option is deleted. * * @since 2.9.0 * * @param string $option Name of the option to delete. */ do_action( 'delete_option', $option ); $result = $wpdb->delete( $wpdb->options, array( 'option_name' => $option ) ); if ( ! wp_installing() ) { if ( in_array( $row->autoload, wp_autoload_values_to_autoload(), true ) ) { $alloptions = wp_load_alloptions( true ); if ( is_array( $alloptions ) && isset( $alloptions[ $option ] ) ) { unset( $alloptions[ $option ] ); wp_cache_set( 'alloptions', $alloptions, 'options' ); } } else { wp_cache_delete( $option, 'options' ); } $notoptions = wp_cache_get( 'notoptions', 'options' ); if ( ! is_array( $notoptions ) ) { $notoptions = array(); } $notoptions[ $option ] = true; wp_cache_set( 'notoptions', $notoptions, 'options' ); } if ( $result ) { /** * Fires after a specific option has been deleted. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * @since 3.0.0 * * @param string $option Name of the deleted option. */ do_action( "delete_option_{$option}", $option ); /** * Fires after an option has been deleted. * * @since 2.9.0 * * @param string $option Name of the deleted option. */ do_action( 'deleted_option', $option ); return true; } return false; } /** * Determines the appropriate autoload value for an option based on input. * * This function checks the provided autoload value and returns a standardized value * ('on', 'off', 'auto-on', 'auto-off', or 'auto') based on specific conditions. * * If no explicit autoload value is provided, the function will check for certain heuristics around the given option. * It will return `auto-on` to indicate autoloading, `auto-off` to indicate not autoloading, or `auto` if no clear * decision could be made. * * @since 6.6.0 * @access private * * @param string $option The name of the option. * @param mixed $value The value of the option to check its autoload value. * @param mixed $serialized_value The serialized value of the option to check its autoload value. * @param bool|null $autoload The autoload value to check. * Accepts 'on'|true to enable or 'off'|false to disable, or * 'auto-on', 'auto-off', or 'auto' for internal purposes. * Any other autoload value will be forced to either 'auto-on', * 'auto-off', or 'auto'. * 'yes' and 'no' are supported for backward compatibility. * @return string Returns the original $autoload value if explicit, or 'auto-on', 'auto-off', * or 'auto' depending on default heuristics. */ function wp_determine_option_autoload_value( $option, $value, $serialized_value, $autoload ) { // Check if autoload is a boolean. if ( is_bool( $autoload ) ) { return $autoload ? 'on' : 'off'; } switch ( $autoload ) { case 'on': case 'yes': return 'on'; case 'off': case 'no': return 'off'; } /** * Allows to determine the default autoload value for an option where no explicit value is passed. * * @since 6.6.0 * * @param bool|null $autoload The default autoload value to set. Returning true will be set as 'auto-on' in the * database, false will be set as 'auto-off', and null will be set as 'auto'. * @param string $option The passed option name. * @param mixed $value The passed option value to be saved. */ $autoload = apply_filters( 'wp_default_autoload_value', null, $option, $value, $serialized_value ); if ( is_bool( $autoload ) ) { return $autoload ? 'auto-on' : 'auto-off'; } return 'auto'; } /** * Filters the default autoload value to disable autoloading if the option value is too large. * * @since 6.6.0 * @access private * * @param bool|null $autoload The default autoload value to set. * @param string $option The passed option name. * @param mixed $value The passed option value to be saved. * @param mixed $serialized_value The passed option value to be saved, in serialized form. * @return bool|null Potentially modified $default. */ function wp_filter_default_autoload_value_via_option_size( $autoload, $option, $value, $serialized_value ) { /** * Filters the maximum size of option value in bytes. * * @since 6.6.0 * * @param int $max_option_size The option-size threshold, in bytes. Default 150000. * @param string $option The name of the option. */ $max_option_size = (int) apply_filters( 'wp_max_autoloaded_option_size', 150000, $option ); $size = ! empty( $serialized_value ) ? strlen( $serialized_value ) : 0; if ( $size > $max_option_size ) { return false; } return $autoload; } /** * Deletes a transient. * * @since 2.8.0 * * @param string $transient Transient name. Expected to not be SQL-escaped. * @return bool True if the transient was deleted, false otherwise. */ function delete_transient( $transient ) { /** * Fires immediately before a specific transient is deleted. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 3.0.0 * * @param string $transient Transient name. */ do_action( "delete_transient_{$transient}", $transient ); if ( wp_using_ext_object_cache() || wp_installing() ) { $result = wp_cache_delete( $transient, 'transient' ); } else { $option_timeout = '_transient_timeout_' . $transient; $option = '_transient_' . $transient; $result = delete_option( $option ); if ( $result ) { delete_option( $option_timeout ); } } if ( $result ) { /** * Fires after a transient is deleted. * * @since 3.0.0 * * @param string $transient Deleted transient name. */ do_action( 'deleted_transient', $transient ); } return $result; } /** * Retrieves the value of a transient. * * If the transient does not exist, does not have a value, or has expired, * then the return value will be false. * * @since 2.8.0 * * @param string $transient Transient name. Expected to not be SQL-escaped. * @return mixed Value of transient. */ function get_transient( $transient ) { /** * Filters the value of an existing transient before it is retrieved. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * Returning a value other than false from the filter will short-circuit retrieval * and return that value instead. * * @since 2.8.0 * @since 4.4.0 The `$transient` parameter was added * * @param mixed $pre_transient The default value to return if the transient does not exist. * Any value other than false will short-circuit the retrieval * of the transient, and return that value. * @param string $transient Transient name. */ $pre = apply_filters( "pre_transient_{$transient}", false, $transient ); if ( false !== $pre ) { return $pre; } if ( wp_using_ext_object_cache() || wp_installing() ) { $value = wp_cache_get( $transient, 'transient' ); } else { $transient_option = '_transient_' . $transient; if ( ! wp_installing() ) { // If option is not in alloptions, it is not autoloaded and thus has a timeout. $alloptions = wp_load_alloptions(); if ( ! isset( $alloptions[ $transient_option ] ) ) { $transient_timeout = '_transient_timeout_' . $transient; wp_prime_option_caches( array( $transient_option, $transient_timeout ) ); $timeout = get_option( $transient_timeout ); if ( false !== $timeout && $timeout < time() ) { delete_option( $transient_option ); delete_option( $transient_timeout ); $value = false; } } } if ( ! isset( $value ) ) { $value = get_option( $transient_option ); } } /** * Filters an existing transient's value. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 2.8.0 * @since 4.4.0 The `$transient` parameter was added * * @param mixed $value Value of transient. * @param string $transient Transient name. */ return apply_filters( "transient_{$transient}", $value, $transient ); } /** * Sets/updates the value of a transient. * * You do not need to serialize values. If the value needs to be serialized, * then it will be serialized before it is set. * * @since 2.8.0 * * @param string $transient Transient name. Expected to not be SQL-escaped. * Must be 172 characters or fewer in length. * @param mixed $value Transient value. Must be serializable if non-scalar. * Expected to not be SQL-escaped. * @param int $expiration Optional. Time until expiration in seconds. Default 0 (no expiration). * @return bool True if the value was set, false otherwise. */ function set_transient( $transient, $value, $expiration = 0 ) { $expiration = (int) $expiration; /** * Filters a specific transient before its value is set. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 3.0.0 * @since 4.2.0 The `$expiration` parameter was added. * @since 4.4.0 The `$transient` parameter was added. * * @param mixed $value New value of transient. * @param int $expiration Time until expiration in seconds. * @param string $transient Transient name. */ $value = apply_filters( "pre_set_transient_{$transient}", $value, $expiration, $transient ); /** * Filters the expiration for a transient before its value is set. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 4.4.0 * * @param int $expiration Time until expiration in seconds. Use 0 for no expiration. * @param mixed $value New value of transient. * @param string $transient Transient name. */ $expiration = apply_filters( "expiration_of_transient_{$transient}", $expiration, $value, $transient ); if ( wp_using_ext_object_cache() || wp_installing() ) { $result = wp_cache_set( $transient, $value, 'transient', $expiration ); } else { $transient_timeout = '_transient_timeout_' . $transient; $transient_option = '_transient_' . $transient; wp_prime_option_caches( array( $transient_option, $transient_timeout ) ); if ( false === get_option( $transient_option ) ) { $autoload = true; if ( $expiration ) { $autoload = false; add_option( $transient_timeout, time() + $expiration, '', false ); } $result = add_option( $transient_option, $value, '', $autoload ); } else { /* * If expiration is requested, but the transient has no timeout option, * delete, then re-create transient rather than update. */ $update = true; if ( $expiration ) { if ( false === get_option( $transient_timeout ) ) { delete_option( $transient_option ); add_option( $transient_timeout, time() + $expiration, '', false ); $result = add_option( $transient_option, $value, '', false ); $update = false; } else { update_option( $transient_timeout, time() + $expiration ); } } if ( $update ) { $result = update_option( $transient_option, $value ); } } } if ( $result ) { /** * Fires after the value for a specific transient has been set. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 3.0.0 * @since 3.6.0 The `$value` and `$expiration` parameters were added. * @since 4.4.0 The `$transient` parameter was added. * * @param mixed $value Transient value. * @param int $expiration Time until expiration in seconds. * @param string $transient The name of the transient. */ do_action( "set_transient_{$transient}", $value, $expiration, $transient ); /** * Fires after the value for a transient has been set. * * @since 3.0.0 * @since 3.6.0 The `$value` and `$expiration` parameters were added. * * @param string $transient The name of the transient. * @param mixed $value Transient value. * @param int $expiration Time until expiration in seconds. */ do_action( 'setted_transient', $transient, $value, $expiration ); } return $result; } /** * Deletes all expired transients. * * Note that this function won't do anything if an external object cache is in use. * * The multi-table delete syntax is used to delete the transient record * from table a, and the corresponding transient_timeout record from table b. * * @global wpdb $wpdb WordPress database abstraction object. * * @since 4.9.0 * * @param bool $force_db Optional. Force cleanup to run against the database even when an external object cache is used. */ function delete_expired_transients( $force_db = false ) { global $wpdb; if ( ! $force_db && wp_using_ext_object_cache() ) { return; } $wpdb->query( $wpdb->prepare( "DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b WHERE a.option_name LIKE %s AND a.option_name NOT LIKE %s AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) ) AND b.option_value < %d", $wpdb->esc_like( '_transient_' ) . '%', $wpdb->esc_like( '_transient_timeout_' ) . '%', time() ) ); if ( ! is_multisite() ) { // Single site stores site transients in the options table. $wpdb->query( $wpdb->prepare( "DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b WHERE a.option_name LIKE %s AND a.option_name NOT LIKE %s AND b.option_name = CONCAT( '_site_transient_timeout_', SUBSTRING( a.option_name, 17 ) ) AND b.option_value < %d", $wpdb->esc_like( '_site_transient_' ) . '%', $wpdb->esc_like( '_site_transient_timeout_' ) . '%', time() ) ); } elseif ( is_multisite() && is_main_site() && is_main_network() ) { // Multisite stores site transients in the sitemeta table. $wpdb->query( $wpdb->prepare( "DELETE a, b FROM {$wpdb->sitemeta} a, {$wpdb->sitemeta} b WHERE a.meta_key LIKE %s AND a.meta_key NOT LIKE %s AND b.meta_key = CONCAT( '_site_transient_timeout_', SUBSTRING( a.meta_key, 17 ) ) AND b.meta_value < %d", $wpdb->esc_like( '_site_transient_' ) . '%', $wpdb->esc_like( '_site_transient_timeout_' ) . '%', time() ) ); } } /** * Saves and restores user interface settings stored in a cookie. * * Checks if the current user-settings cookie is updated and stores it. When no * cookie exists (different browser used), adds the last saved cookie restoring * the settings. * * @since 2.7.0 */ function wp_user_settings() { if ( ! is_admin() || wp_doing_ajax() ) { return; } $user_id = get_current_user_id(); if ( ! $user_id ) { return; } if ( ! is_user_member_of_blog() ) { return; } $settings = (string) get_user_option( 'user-settings', $user_id ); if ( isset( $_COOKIE[ 'wp-settings-' . $user_id ] ) ) { $cookie = preg_replace( '/[^A-Za-z0-9=&_]/', '', $_COOKIE[ 'wp-settings-' . $user_id ] ); // No change or both empty. if ( $cookie === $settings ) { return; } $last_saved = (int) get_user_option( 'user-settings-time', $user_id ); $current = isset( $_COOKIE[ 'wp-settings-time-' . $user_id ] ) ? preg_replace( '/[^0-9]/', '', $_COOKIE[ 'wp-settings-time-' . $user_id ] ) : 0; // The cookie is newer than the saved value. Update the user_option and leave the cookie as-is. if ( $current > $last_saved ) { update_user_option( $user_id, 'user-settings', $cookie, false ); update_user_option( $user_id, 'user-settings-time', time() - 5, false ); return; } } // The cookie is not set in the current browser or the saved value is newer. $secure = ( 'https' === parse_url( admin_url(), PHP_URL_SCHEME ) ); setcookie( 'wp-settings-' . $user_id, $settings, time() + YEAR_IN_SECONDS, SITECOOKIEPATH, '', $secure ); setcookie( 'wp-settings-time-' . $user_id, time(), time() + YEAR_IN_SECONDS, SITECOOKIEPATH, '', $secure ); $_COOKIE[ 'wp-settings-' . $user_id ] = $settings; } /** * Retrieves user interface setting value based on setting name. * * @since 2.7.0 * * @param string $name The name of the setting. * @param string|false $default_value Optional. Default value to return when $name is not set. Default false. * @return mixed The last saved user setting or the default value/false if it doesn't exist. */ function get_user_setting( $name, $default_value = false ) { $all_user_settings = get_all_user_settings(); return isset( $all_user_settings[ $name ] ) ? $all_user_settings[ $name ] : $default_value; } /** * Adds or updates user interface setting. * * Both `$name` and `$value` can contain only ASCII letters, numbers, hyphens, and underscores. * * This function has to be used before any output has started as it calls `setcookie()`. * * @since 2.8.0 * * @param string $name The name of the setting. * @param string $value The value for the setting. * @return bool|null True if set successfully, false otherwise. * Null if the current user is not a member of the site. */ function set_user_setting( $name, $value ) { if ( headers_sent() ) { return false; } $all_user_settings = get_all_user_settings(); $all_user_settings[ $name ] = $value; return wp_set_all_user_settings( $all_user_settings ); } /** * Deletes user interface settings. * * Deleting settings would reset them to the defaults. * * This function has to be used before any output has started as it calls `setcookie()`. * * @since 2.7.0 * * @param string $names The name or array of names of the setting to be deleted. * @return bool|null True if deleted successfully, false otherwise. * Null if the current user is not a member of the site. */ function delete_user_setting( $names ) { if ( headers_sent() ) { return false; } $all_user_settings = get_all_user_settings(); $names = (array) $names; $deleted = false; foreach ( $names as $name ) { if ( isset( $all_user_settings[ $name ] ) ) { unset( $all_user_settings[ $name ] ); $deleted = true; } } if ( $deleted ) { return wp_set_all_user_settings( $all_user_settings ); } return false; } /** * Retrieves all user interface settings. * * @since 2.7.0 * * @global array $_updated_user_settings * * @return array The last saved user settings or empty array. */ function get_all_user_settings() { global $_updated_user_settings; $user_id = get_current_user_id(); if ( ! $user_id ) { return array(); } if ( isset( $_updated_user_settings ) && is_array( $_updated_user_settings ) ) { return $_updated_user_settings; } $user_settings = array(); if ( isset( $_COOKIE[ 'wp-settings-' . $user_id ] ) ) { $cookie = preg_replace( '/[^A-Za-z0-9=&_-]/', '', $_COOKIE[ 'wp-settings-' . $user_id ] ); if ( strpos( $cookie, '=' ) ) { // '=' cannot be 1st char. parse_str( $cookie, $user_settings ); } } else { $option = get_user_option( 'user-settings', $user_id ); if ( $option && is_string( $option ) ) { parse_str( $option, $user_settings ); } } $_updated_user_settings = $user_settings; return $user_settings; } /** * Private. Sets all user interface settings. * * @since 2.8.0 * @access private * * @global array $_updated_user_settings * * @param array $user_settings User settings. * @return bool|null True if set successfully, false if the current user could not be found. * Null if the current user is not a member of the site. */ function wp_set_all_user_settings( $user_settings ) { global $_updated_user_settings; $user_id = get_current_user_id(); if ( ! $user_id ) { return false; } if ( ! is_user_member_of_blog() ) { return; } $settings = ''; foreach ( $user_settings as $name => $value ) { $_name = preg_replace( '/[^A-Za-z0-9_-]+/', '', $name ); $_value = preg_replace( '/[^A-Za-z0-9_-]+/', '', $value ); if ( ! empty( $_name ) ) { $settings .= $_name . '=' . $_value . '&'; } } $settings = rtrim( $settings, '&' ); parse_str( $settings, $_updated_user_settings ); update_user_option( $user_id, 'user-settings', $settings, false ); update_user_option( $user_id, 'user-settings-time', time(), false ); return true; } /** * Deletes the user settings of the current user. * * @since 2.7.0 */ function delete_all_user_settings() { $user_id = get_current_user_id(); if ( ! $user_id ) { return; } update_user_option( $user_id, 'user-settings', '', false ); setcookie( 'wp-settings-' . $user_id, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH ); } /** * Retrieve an option value for the current network based on name of option. * * @since 2.8.0 * @since 4.4.0 The `$use_cache` parameter was deprecated. * @since 4.4.0 Modified into wrapper for get_network_option() * * @see get_network_option() * * @param string $option Name of the option to retrieve. Expected to not be SQL-escaped. * @param mixed $default_value Optional. Value to return if the option doesn't exist. Default false. * @param bool $deprecated Whether to use cache. Multisite only. Always set to true. * @return mixed Value set for the option. */ function get_site_option( $option, $default_value = false, $deprecated = true ) { return get_network_option( null, $option, $default_value ); } /** * Adds a new option for the current network. * * Existing options will not be updated. Note that prior to 3.3 this wasn't the case. * * @since 2.8.0 * @since 4.4.0 Modified into wrapper for add_network_option() * * @see add_network_option() * * @param string $option Name of the option to add. Expected to not be SQL-escaped. * @param mixed $value Option value, can be anything. Expected to not be SQL-escaped. * @return bool True if the option was added, false otherwise. */ function add_site_option( $option, $value ) { return add_network_option( null, $option, $value ); } /** * Removes an option by name for the current network. * * @since 2.8.0 * @since 4.4.0 Modified into wrapper for delete_network_option() * * @see delete_network_option() * * @param string $option Name of the option to delete. Expected to not be SQL-escaped. * @return bool True if the option was deleted, false otherwise. */ function delete_site_option( $option ) { return delete_network_option( null, $option ); } /** * Updates the value of an option that was already added for the current network. * * @since 2.8.0 * @since 4.4.0 Modified into wrapper for update_network_option() * * @see update_network_option() * * @param string $option Name of the option. Expected to not be SQL-escaped. * @param mixed $value Option value. Expected to not be SQL-escaped. * @return bool True if the value was updated, false otherwise. */ function update_site_option( $option, $value ) { return update_network_option( null, $option, $value ); } /** * Retrieves a network's option value based on the option name. * * @since 4.4.0 * * @see get_option() * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $network_id ID of the network. Can be null to default to the current network ID. * @param string $option Name of the option to retrieve. Expected to not be SQL-escaped. * @param mixed $default_value Optional. Value to return if the option doesn't exist. Default false. * @return mixed Value set for the option. */ function get_network_option( $network_id, $option, $default_value = false ) { global $wpdb; if ( $network_id && ! is_numeric( $network_id ) ) { return false; } $network_id = (int) $network_id; // Fallback to the current network if a network ID is not specified. if ( ! $network_id ) { $network_id = get_current_network_id(); } /** * Filters the value of an existing network option before it is retrieved. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * Returning a value other than false from the filter will short-circuit retrieval * and return that value instead. * * @since 2.9.0 As 'pre_site_option_' . $key * @since 3.0.0 * @since 4.4.0 The `$option` parameter was added. * @since 4.7.0 The `$network_id` parameter was added. * @since 4.9.0 The `$default_value` parameter was added. * * @param mixed $pre_site_option The value to return instead of the option value. This differs from * `$default_value`, which is used as the fallback value in the event * the option doesn't exist elsewhere in get_network_option(). * Default false (to skip past the short-circuit). * @param string $option Option name. * @param int $network_id ID of the network. * @param mixed $default_value The fallback value to return if the option does not exist. * Default false. */ $pre = apply_filters( "pre_site_option_{$option}", false, $option, $network_id, $default_value ); if ( false !== $pre ) { return $pre; } // Prevent non-existent options from triggering multiple queries. $notoptions_key = "$network_id:notoptions"; $notoptions = wp_cache_get( $notoptions_key, 'site-options' ); if ( is_array( $notoptions ) && isset( $notoptions[ $option ] ) ) { /** * Filters the value of a specific default network option. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * @since 3.4.0 * @since 4.4.0 The `$option` parameter was added. * @since 4.7.0 The `$network_id` parameter was added. * * @param mixed $default_value The value to return if the site option does not exist * in the database. * @param string $option Option name. * @param int $network_id ID of the network. */ return apply_filters( "default_site_option_{$option}", $default_value, $option, $network_id ); } if ( ! is_multisite() ) { /** This filter is documented in wp-includes/option.php */ $default_value = apply_filters( 'default_site_option_' . $option, $default_value, $option, $network_id ); $value = get_option( $option, $default_value ); } else { $cache_key = "$network_id:$option"; $value = wp_cache_get( $cache_key, 'site-options' ); if ( ! isset( $value ) || false === $value ) { $row = $wpdb->get_row( $wpdb->prepare( "SELECT meta_value FROM $wpdb->sitemeta WHERE meta_key = %s AND site_id = %d", $option, $network_id ) ); // Has to be get_row() instead of get_var() because of funkiness with 0, false, null values. if ( is_object( $row ) ) { $value = $row->meta_value; $value = maybe_unserialize( $value ); wp_cache_set( $cache_key, $value, 'site-options' ); } else { if ( ! is_array( $notoptions ) ) { $notoptions = array(); } $notoptions[ $option ] = true; wp_cache_set( $notoptions_key, $notoptions, 'site-options' ); /** This filter is documented in wp-includes/option.php */ $value = apply_filters( 'default_site_option_' . $option, $default_value, $option, $network_id ); } } } if ( ! is_array( $notoptions ) ) { $notoptions = array(); wp_cache_set( $notoptions_key, $notoptions, 'site-options' ); } /** * Filters the value of an existing network option. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * @since 2.9.0 As 'site_option_' . $key * @since 3.0.0 * @since 4.4.0 The `$option` parameter was added. * @since 4.7.0 The `$network_id` parameter was added. * * @param mixed $value Value of network option. * @param string $option Option name. * @param int $network_id ID of the network. */ return apply_filters( "site_option_{$option}", $value, $option, $network_id ); } /** * Adds a new network option. * * Existing options will not be updated. * * @since 4.4.0 * * @see add_option() * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $network_id ID of the network. Can be null to default to the current network ID. * @param string $option Name of the option to add. Expected to not be SQL-escaped. * @param mixed $value Option value, can be anything. Expected to not be SQL-escaped. * @return bool True if the option was added, false otherwise. */ function add_network_option( $network_id, $option, $value ) { global $wpdb; if ( $network_id && ! is_numeric( $network_id ) ) { return false; } $network_id = (int) $network_id; // Fallback to the current network if a network ID is not specified. if ( ! $network_id ) { $network_id = get_current_network_id(); } wp_protect_special_option( $option ); /** * Filters the value of a specific network option before it is added. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * @since 2.9.0 As 'pre_add_site_option_' . $key * @since 3.0.0 * @since 4.4.0 The `$option` parameter was added. * @since 4.7.0 The `$network_id` parameter was added. * * @param mixed $value Value of network option. * @param string $option Option name. * @param int $network_id ID of the network. */ $value = apply_filters( "pre_add_site_option_{$option}", $value, $option, $network_id ); $notoptions_key = "$network_id:notoptions"; if ( ! is_multisite() ) { $result = add_option( $option, $value, '', false ); } else { $cache_key = "$network_id:$option"; /* * Make sure the option doesn't already exist. * We can check the 'notoptions' cache before we ask for a DB query. */ $notoptions = wp_cache_get( $notoptions_key, 'site-options' ); if ( ! is_array( $notoptions ) || ! isset( $notoptions[ $option ] ) ) { if ( false !== get_network_option( $network_id, $option, false ) ) { return false; } } $value = sanitize_option( $option, $value ); $serialized_value = maybe_serialize( $value ); $result = $wpdb->insert( $wpdb->sitemeta, array( 'site_id' => $network_id, 'meta_key' => $option, 'meta_value' => $serialized_value, ) ); if ( ! $result ) { return false; } wp_cache_set( $cache_key, $value, 'site-options' ); // This option exists now. $notoptions = wp_cache_get( $notoptions_key, 'site-options' ); // Yes, again... we need it to be fresh. if ( is_array( $notoptions ) && isset( $notoptions[ $option ] ) ) { unset( $notoptions[ $option ] ); wp_cache_set( $notoptions_key, $notoptions, 'site-options' ); } } if ( $result ) { /** * Fires after a specific network option has been successfully added. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * @since 2.9.0 As "add_site_option_{$key}" * @since 3.0.0 * @since 4.7.0 The `$network_id` parameter was added. * * @param string $option Name of the network option. * @param mixed $value Value of the network option. * @param int $network_id ID of the network. */ do_action( "add_site_option_{$option}", $option, $value, $network_id ); /** * Fires after a network option has been successfully added. * * @since 3.0.0 * @since 4.7.0 The `$network_id` parameter was added. * * @param string $option Name of the network option. * @param mixed $value Value of the network option. * @param int $network_id ID of the network. */ do_action( 'add_site_option', $option, $value, $network_id ); return true; } return false; } /** * Removes a network option by name. * * @since 4.4.0 * * @see delete_option() * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $network_id ID of the network. Can be null to default to the current network ID. * @param string $option Name of the option to delete. Expected to not be SQL-escaped. * @return bool True if the option was deleted, false otherwise. */ function delete_network_option( $network_id, $option ) { global $wpdb; if ( $network_id && ! is_numeric( $network_id ) ) { return false; } $network_id = (int) $network_id; // Fallback to the current network if a network ID is not specified. if ( ! $network_id ) { $network_id = get_current_network_id(); } /** * Fires immediately before a specific network option is deleted. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * @since 3.0.0 * @since 4.4.0 The `$option` parameter was added. * @since 4.7.0 The `$network_id` parameter was added. * * @param string $option Option name. * @param int $network_id ID of the network. */ do_action( "pre_delete_site_option_{$option}", $option, $network_id ); if ( ! is_multisite() ) { $result = delete_option( $option ); } else { $row = $wpdb->get_row( $wpdb->prepare( "SELECT meta_id FROM {$wpdb->sitemeta} WHERE meta_key = %s AND site_id = %d", $option, $network_id ) ); if ( is_null( $row ) || ! $row->meta_id ) { return false; } $cache_key = "$network_id:$option"; wp_cache_delete( $cache_key, 'site-options' ); $result = $wpdb->delete( $wpdb->sitemeta, array( 'meta_key' => $option, 'site_id' => $network_id, ) ); if ( $result ) { $notoptions_key = "$network_id:notoptions"; $notoptions = wp_cache_get( $notoptions_key, 'site-options' ); if ( ! is_array( $notoptions ) ) { $notoptions = array(); } $notoptions[ $option ] = true; wp_cache_set( $notoptions_key, $notoptions, 'site-options' ); } } if ( $result ) { /** * Fires after a specific network option has been deleted. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * @since 2.9.0 As "delete_site_option_{$key}" * @since 3.0.0 * @since 4.7.0 The `$network_id` parameter was added. * * @param string $option Name of the network option. * @param int $network_id ID of the network. */ do_action( "delete_site_option_{$option}", $option, $network_id ); /** * Fires after a network option has been deleted. * * @since 3.0.0 * @since 4.7.0 The `$network_id` parameter was added. * * @param string $option Name of the network option. * @param int $network_id ID of the network. */ do_action( 'delete_site_option', $option, $network_id ); return true; } return false; } /** * Updates the value of a network option that was already added. * * @since 4.4.0 * * @see update_option() * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $network_id ID of the network. Can be null to default to the current network ID. * @param string $option Name of the option. Expected to not be SQL-escaped. * @param mixed $value Option value. Expected to not be SQL-escaped. * @return bool True if the value was updated, false otherwise. */ function update_network_option( $network_id, $option, $value ) { global $wpdb; if ( $network_id && ! is_numeric( $network_id ) ) { return false; } $network_id = (int) $network_id; // Fallback to the current network if a network ID is not specified. if ( ! $network_id ) { $network_id = get_current_network_id(); } wp_protect_special_option( $option ); $old_value = get_network_option( $network_id, $option ); /** * Filters a specific network option before its value is updated. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * @since 2.9.0 As 'pre_update_site_option_' . $key * @since 3.0.0 * @since 4.4.0 The `$option` parameter was added. * @since 4.7.0 The `$network_id` parameter was added. * * @param mixed $value New value of the network option. * @param mixed $old_value Old value of the network option. * @param string $option Option name. * @param int $network_id ID of the network. */ $value = apply_filters( "pre_update_site_option_{$option}", $value, $old_value, $option, $network_id ); /* * If the new and old values are the same, no need to update. * * Unserialized values will be adequate in most cases. If the unserialized * data differs, the (maybe) serialized data is checked to avoid * unnecessary database calls for otherwise identical object instances. * * See https://core.trac.wordpress.org/ticket/44956 */ if ( $value === $old_value || maybe_serialize( $value ) === maybe_serialize( $old_value ) ) { return false; } if ( false === $old_value ) { return add_network_option( $network_id, $option, $value ); } $notoptions_key = "$network_id:notoptions"; $notoptions = wp_cache_get( $notoptions_key, 'site-options' ); if ( is_array( $notoptions ) && isset( $notoptions[ $option ] ) ) { unset( $notoptions[ $option ] ); wp_cache_set( $notoptions_key, $notoptions, 'site-options' ); } if ( ! is_multisite() ) { $result = update_option( $option, $value, false ); } else { $value = sanitize_option( $option, $value ); $serialized_value = maybe_serialize( $value ); $result = $wpdb->update( $wpdb->sitemeta, array( 'meta_value' => $serialized_value ), array( 'site_id' => $network_id, 'meta_key' => $option, ) ); if ( $result ) { $cache_key = "$network_id:$option"; wp_cache_set( $cache_key, $value, 'site-options' ); } } if ( $result ) { /** * Fires after the value of a specific network option has been successfully updated. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * @since 2.9.0 As "update_site_option_{$key}" * @since 3.0.0 * @since 4.7.0 The `$network_id` parameter was added. * * @param string $option Name of the network option. * @param mixed $value Current value of the network option. * @param mixed $old_value Old value of the network option. * @param int $network_id ID of the network. */ do_action( "update_site_option_{$option}", $option, $value, $old_value, $network_id ); /** * Fires after the value of a network option has been successfully updated. * * @since 3.0.0 * @since 4.7.0 The `$network_id` parameter was added. * * @param string $option Name of the network option. * @param mixed $value Current value of the network option. * @param mixed $old_value Old value of the network option. * @param int $network_id ID of the network. */ do_action( 'update_site_option', $option, $value, $old_value, $network_id ); return true; } return false; } /** * Deletes a site transient. * * @since 2.9.0 * * @param string $transient Transient name. Expected to not be SQL-escaped. * @return bool True if the transient was deleted, false otherwise. */ function delete_site_transient( $transient ) { /** * Fires immediately before a specific site transient is deleted. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 3.0.0 * * @param string $transient Transient name. */ do_action( "delete_site_transient_{$transient}", $transient ); if ( wp_using_ext_object_cache() || wp_installing() ) { $result = wp_cache_delete( $transient, 'site-transient' ); } else { $option_timeout = '_site_transient_timeout_' . $transient; $option = '_site_transient_' . $transient; $result = delete_site_option( $option ); if ( $result ) { delete_site_option( $option_timeout ); } } if ( $result ) { /** * Fires after a transient is deleted. * * @since 3.0.0 * * @param string $transient Deleted transient name. */ do_action( 'deleted_site_transient', $transient ); } return $result; } /** * Retrieves the value of a site transient. * * If the transient does not exist, does not have a value, or has expired, * then the return value will be false. * * @since 2.9.0 * * @see get_transient() * * @param string $transient Transient name. Expected to not be SQL-escaped. * @return mixed Value of transient. */ function get_site_transient( $transient ) { /** * Filters the value of an existing site transient before it is retrieved. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * Returning a value other than boolean false will short-circuit retrieval and * return that value instead. * * @since 2.9.0 * @since 4.4.0 The `$transient` parameter was added. * * @param mixed $pre_site_transient The default value to return if the site transient does not exist. * Any value other than false will short-circuit the retrieval * of the transient, and return that value. * @param string $transient Transient name. */ $pre = apply_filters( "pre_site_transient_{$transient}", false, $transient ); if ( false !== $pre ) { return $pre; } if ( wp_using_ext_object_cache() || wp_installing() ) { $value = wp_cache_get( $transient, 'site-transient' ); } else { // Core transients that do not have a timeout. Listed here so querying timeouts can be avoided. $no_timeout = array( 'update_core', 'update_plugins', 'update_themes' ); $transient_option = '_site_transient_' . $transient; if ( ! in_array( $transient, $no_timeout, true ) ) { $transient_timeout = '_site_transient_timeout_' . $transient; wp_prime_site_option_caches( array( $transient_option, $transient_timeout ) ); $timeout = get_site_option( $transient_timeout ); if ( false !== $timeout && $timeout < time() ) { delete_site_option( $transient_option ); delete_site_option( $transient_timeout ); $value = false; } } if ( ! isset( $value ) ) { $value = get_site_option( $transient_option ); } } /** * Filters the value of an existing site transient. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 2.9.0 * @since 4.4.0 The `$transient` parameter was added. * * @param mixed $value Value of site transient. * @param string $transient Transient name. */ return apply_filters( "site_transient_{$transient}", $value, $transient ); } /** * Sets/updates the value of a site transient. * * You do not need to serialize values. If the value needs to be serialized, * then it will be serialized before it is set. * * @since 2.9.0 * * @see set_transient() * * @param string $transient Transient name. Expected to not be SQL-escaped. Must be * 167 characters or fewer in length. * @param mixed $value Transient value. Expected to not be SQL-escaped. * @param int $expiration Optional. Time until expiration in seconds. Default 0 (no expiration). * @return bool True if the value was set, false otherwise. */ function set_site_transient( $transient, $value, $expiration = 0 ) { /** * Filters the value of a specific site transient before it is set. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 3.0.0 * @since 4.4.0 The `$transient` parameter was added. * * @param mixed $value New value of site transient. * @param string $transient Transient name. */ $value = apply_filters( "pre_set_site_transient_{$transient}", $value, $transient ); $expiration = (int) $expiration; /** * Filters the expiration for a site transient before its value is set. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 4.4.0 * * @param int $expiration Time until expiration in seconds. Use 0 for no expiration. * @param mixed $value New value of site transient. * @param string $transient Transient name. */ $expiration = apply_filters( "expiration_of_site_transient_{$transient}", $expiration, $value, $transient ); if ( wp_using_ext_object_cache() || wp_installing() ) { $result = wp_cache_set( $transient, $value, 'site-transient', $expiration ); } else { $transient_timeout = '_site_transient_timeout_' . $transient; $option = '_site_transient_' . $transient; wp_prime_site_option_caches( array( $option, $transient_timeout ) ); if ( false === get_site_option( $option ) ) { if ( $expiration ) { add_site_option( $transient_timeout, time() + $expiration ); } $result = add_site_option( $option, $value ); } else { if ( $expiration ) { update_site_option( $transient_timeout, time() + $expiration ); } $result = update_site_option( $option, $value ); } } if ( $result ) { /** * Fires after the value for a specific site transient has been set. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 3.0.0 * @since 4.4.0 The `$transient` parameter was added * * @param mixed $value Site transient value. * @param int $expiration Time until expiration in seconds. * @param string $transient Transient name. */ do_action( "set_site_transient_{$transient}", $value, $expiration, $transient ); /** * Fires after the value for a site transient has been set. * * @since 3.0.0 * * @param string $transient The name of the site transient. * @param mixed $value Site transient value. * @param int $expiration Time until expiration in seconds. */ do_action( 'setted_site_transient', $transient, $value, $expiration ); } return $result; } /** * Registers default settings available in WordPress. * * The settings registered here are primarily useful for the REST API, so this * does not encompass all settings available in WordPress. * * @since 4.7.0 * @since 6.0.1 The `show_on_front`, `page_on_front`, and `page_for_posts` options were added. */ function register_initial_settings() { register_setting( 'general', 'blogname', array( 'show_in_rest' => array( 'name' => 'title', ), 'type' => 'string', 'label' => __( 'Title' ), 'description' => __( 'Site title.' ), ) ); register_setting( 'general', 'blogdescription', array( 'show_in_rest' => array( 'name' => 'description', ), 'type' => 'string', 'label' => __( 'Tagline' ), 'description' => __( 'Site tagline.' ), ) ); if ( ! is_multisite() ) { register_setting( 'general', 'siteurl', array( 'show_in_rest' => array( 'name' => 'url', 'schema' => array( 'format' => 'uri', ), ), 'type' => 'string', 'description' => __( 'Site URL.' ), ) ); } if ( ! is_multisite() ) { register_setting( 'general', 'admin_email', array( 'show_in_rest' => array( 'name' => 'email', 'schema' => array( 'format' => 'email', ), ), 'type' => 'string', 'description' => __( 'This address is used for admin purposes, like new user notification.' ), ) ); } register_setting( 'general', 'timezone_string', array( 'show_in_rest' => array( 'name' => 'timezone', ), 'type' => 'string', 'description' => __( 'A city in the same timezone as you.' ), ) ); register_setting( 'general', 'date_format', array( 'show_in_rest' => true, 'type' => 'string', 'description' => __( 'A date format for all date strings.' ), ) ); register_setting( 'general', 'time_format', array( 'show_in_rest' => true, 'type' => 'string', 'description' => __( 'A time format for all time strings.' ), ) ); register_setting( 'general', 'start_of_week', array( 'show_in_rest' => true, 'type' => 'integer', 'description' => __( 'A day number of the week that the week should start on.' ), ) ); register_setting( 'general', 'WPLANG', array( 'show_in_rest' => array( 'name' => 'language', ), 'type' => 'string', 'description' => __( 'WordPress locale code.' ), 'default' => 'en_US', ) ); register_setting( 'writing', 'use_smilies', array( 'show_in_rest' => true, 'type' => 'boolean', 'description' => __( 'Convert emoticons like :-) and :-P to graphics on display.' ), 'default' => true, ) ); register_setting( 'writing', 'default_category', array( 'show_in_rest' => true, 'type' => 'integer', 'description' => __( 'Default post category.' ), ) ); register_setting( 'writing', 'default_post_format', array( 'show_in_rest' => true, 'type' => 'string', 'description' => __( 'Default post format.' ), ) ); register_setting( 'reading', 'posts_per_page', array( 'show_in_rest' => true, 'type' => 'integer', 'label' => __( 'Maximum posts per page' ), 'description' => __( 'Blog pages show at most.' ), 'default' => 10, ) ); register_setting( 'reading', 'show_on_front', array( 'show_in_rest' => true, 'type' => 'string', 'label' => __( 'Show on front' ), 'description' => __( 'What to show on the front page' ), ) ); register_setting( 'reading', 'page_on_front', array( 'show_in_rest' => true, 'type' => 'integer', 'label' => __( 'Page on front' ), 'description' => __( 'The ID of the page that should be displayed on the front page' ), ) ); register_setting( 'reading', 'page_for_posts', array( 'show_in_rest' => true, 'type' => 'integer', 'description' => __( 'The ID of the page that should display the latest posts' ), ) ); register_setting( 'discussion', 'default_ping_status', array( 'show_in_rest' => array( 'schema' => array( 'enum' => array( 'open', 'closed' ), ), ), 'type' => 'string', 'description' => __( 'Allow link notifications from other blogs (pingbacks and trackbacks) on new articles.' ), ) ); register_setting( 'discussion', 'default_comment_status', array( 'show_in_rest' => array( 'schema' => array( 'enum' => array( 'open', 'closed' ), ), ), 'type' => 'string', 'label' => __( 'Allow comments on new posts' ), 'description' => __( 'Allow people to submit comments on new posts.' ), ) ); } /** * Registers a setting and its data. * * @since 2.7.0 * @since 3.0.0 The `misc` option group was deprecated. * @since 3.5.0 The `privacy` option group was deprecated. * @since 4.7.0 `$args` can be passed to set flags on the setting, similar to `register_meta()`. * @since 5.5.0 `$new_whitelist_options` was renamed to `$new_allowed_options`. * Please consider writing more inclusive code. * @since 6.6.0 Added the `label` argument. * * @global array $new_allowed_options * @global array $wp_registered_settings * * @param string $option_group A settings group name. Should correspond to an allowed option key name. * Default allowed option key names include 'general', 'discussion', 'media', * 'reading', 'writing', and 'options'. * @param string $option_name The name of an option to sanitize and save. * @param array $args { * Data used to describe the setting when registered. * * @type string $type The type of data associated with this setting. * Valid values are 'string', 'boolean', 'integer', 'number', 'array', and 'object'. * @type string $label A label of the data attached to this setting. * @type string $description A description of the data attached to this setting. * @type callable $sanitize_callback A callback function that sanitizes the option's value. * @type bool|array $show_in_rest Whether data associated with this setting should be included in the REST API. * When registering complex settings, this argument may optionally be an * array with a 'schema' key. * @type mixed $default Default value when calling `get_option()`. * } */ function register_setting( $option_group, $option_name, $args = array() ) { global $new_allowed_options, $wp_registered_settings; /* * In 5.5.0, the `$new_whitelist_options` global variable was renamed to `$new_allowed_options`. * Please consider writing more inclusive code. */ $GLOBALS['new_whitelist_options'] = &$new_allowed_options; $defaults = array( 'type' => 'string', 'group' => $option_group, 'label' => '', 'description' => '', 'sanitize_callback' => null, 'show_in_rest' => false, ); // Back-compat: old sanitize callback is added. if ( is_callable( $args ) ) { $args = array( 'sanitize_callback' => $args, ); } /** * Filters the registration arguments when registering a setting. * * @since 4.7.0 * * @param array $args Array of setting registration arguments. * @param array $defaults Array of default arguments. * @param string $option_group Setting group. * @param string $option_name Setting name. */ $args = apply_filters( 'register_setting_args', $args, $defaults, $option_group, $option_name ); $args = wp_parse_args( $args, $defaults ); // Require an item schema when registering settings with an array type. if ( false !== $args['show_in_rest'] && 'array' === $args['type'] && ( ! is_array( $args['show_in_rest'] ) || ! isset( $args['show_in_rest']['schema']['items'] ) ) ) { _doing_it_wrong( __FUNCTION__, __( 'When registering an "array" setting to show in the REST API, you must specify the schema for each array item in "show_in_rest.schema.items".' ), '5.4.0' ); } if ( ! is_array( $wp_registered_settings ) ) { $wp_registered_settings = array(); } if ( 'misc' === $option_group ) { _deprecated_argument( __FUNCTION__, '3.0.0', sprintf( /* translators: %s: misc */ __( 'The "%s" options group has been removed. Use another settings group.' ), 'misc' ) ); $option_group = 'general'; } if ( 'privacy' === $option_group ) { _deprecated_argument( __FUNCTION__, '3.5.0', sprintf( /* translators: %s: privacy */ __( 'The "%s" options group has been removed. Use another settings group.' ), 'privacy' ) ); $option_group = 'reading'; } $new_allowed_options[ $option_group ][] = $option_name; if ( ! empty( $args['sanitize_callback'] ) ) { add_filter( "sanitize_option_{$option_name}", $args['sanitize_callback'] ); } if ( array_key_exists( 'default', $args ) ) { add_filter( "default_option_{$option_name}", 'filter_default_option', 10, 3 ); } /** * Fires immediately before the setting is registered but after its filters are in place. * * @since 5.5.0 * * @param string $option_group Setting group. * @param string $option_name Setting name. * @param array $args Array of setting registration arguments. */ do_action( 'register_setting', $option_group, $option_name, $args ); $wp_registered_settings[ $option_name ] = $args; } /** * Unregisters a setting. * * @since 2.7.0 * @since 4.7.0 `$sanitize_callback` was deprecated. The callback from `register_setting()` is now used instead. * @since 5.5.0 `$new_whitelist_options` was renamed to `$new_allowed_options`. * Please consider writing more inclusive code. * * @global array $new_allowed_options * @global array $wp_registered_settings * * @param string $option_group The settings group name used during registration. * @param string $option_name The name of the option to unregister. * @param callable $deprecated Optional. Deprecated. */ function unregister_setting( $option_group, $option_name, $deprecated = '' ) { global $new_allowed_options, $wp_registered_settings; /* * In 5.5.0, the `$new_whitelist_options` global variable was renamed to `$new_allowed_options`. * Please consider writing more inclusive code. */ $GLOBALS['new_whitelist_options'] = &$new_allowed_options; if ( 'misc' === $option_group ) { _deprecated_argument( __FUNCTION__, '3.0.0', sprintf( /* translators: %s: misc */ __( 'The "%s" options group has been removed. Use another settings group.' ), 'misc' ) ); $option_group = 'general'; } if ( 'privacy' === $option_group ) { _deprecated_argument( __FUNCTION__, '3.5.0', sprintf( /* translators: %s: privacy */ __( 'The "%s" options group has been removed. Use another settings group.' ), 'privacy' ) ); $option_group = 'reading'; } $pos = false; if ( isset( $new_allowed_options[ $option_group ] ) ) { $pos = array_search( $option_name, (array) $new_allowed_options[ $option_group ], true ); } if ( false !== $pos ) { unset( $new_allowed_options[ $option_group ][ $pos ] ); } if ( '' !== $deprecated ) { _deprecated_argument( __FUNCTION__, '4.7.0', sprintf( /* translators: 1: $sanitize_callback, 2: register_setting() */ __( '%1$s is deprecated. The callback from %2$s is used instead.' ), '$sanitize_callback', 'register_setting()' ) ); remove_filter( "sanitize_option_{$option_name}", $deprecated ); } if ( isset( $wp_registered_settings[ $option_name ] ) ) { // Remove the sanitize callback if one was set during registration. if ( ! empty( $wp_registered_settings[ $option_name ]['sanitize_callback'] ) ) { remove_filter( "sanitize_option_{$option_name}", $wp_registered_settings[ $option_name ]['sanitize_callback'] ); } // Remove the default filter if a default was provided during registration. if ( array_key_exists( 'default', $wp_registered_settings[ $option_name ] ) ) { remove_filter( "default_option_{$option_name}", 'filter_default_option', 10 ); } /** * Fires immediately before the setting is unregistered and after its filters have been removed. * * @since 5.5.0 * * @param string $option_group Setting group. * @param string $option_name Setting name. */ do_action( 'unregister_setting', $option_group, $option_name ); unset( $wp_registered_settings[ $option_name ] ); } } /** * Retrieves an array of registered settings. * * @since 4.7.0 * * @global array $wp_registered_settings * * @return array List of registered settings, keyed by option name. */ function get_registered_settings() { global $wp_registered_settings; if ( ! is_array( $wp_registered_settings ) ) { return array(); } return $wp_registered_settings; } /** * Filters the default value for the option. * * For settings which register a default setting in `register_setting()`, this * function is added as a filter to `default_option_{$option}`. * * @since 4.7.0 * * @param mixed $default_value Existing default value to return. * @param string $option Option name. * @param bool $passed_default Was `get_option()` passed a default value? * @return mixed Filtered default value. */ function filter_default_option( $default_value, $option, $passed_default ) { if ( $passed_default ) { return $default_value; } $registered = get_registered_settings(); if ( empty( $registered[ $option ] ) ) { return $default_value; } return $registered[ $option ]['default']; } /** * Returns the values that trigger autoloading from the options table. * * @since 6.6.0 * * @return string[] The values that trigger autoloading. */ function wp_autoload_values_to_autoload() { $autoload_values = array( 'yes', 'on', 'auto-on', 'auto' ); /** * Filters the autoload values that should be considered for autoloading from the options table. * * The filter can only be used to remove autoload values from the default list. * * @since 6.6.0 * * @param string[] $autoload_values Autoload values used to autoload option. * Default list contains 'yes', 'on', 'auto-on', and 'auto'. */ $filtered_values = apply_filters( 'wp_autoload_values_to_autoload', $autoload_values ); return array_intersect( $filtered_values, $autoload_values ); }