/** * WordPress implementation for PHP functions either missing from older PHP versions or not included by default. * * @package PHP * @access private */ // If gettext isn't available if ( ! function_exists( '_' ) ) { function _( $string ) { return $string; } } /** * Returns whether PCRE/u (PCRE_UTF8 modifier) is available for use. * * @ignore * @since 4.2.2 * @access private * * @staticvar string $utf8_pcre * * @param bool $set - Used for testing only * null : default - get PCRE/u capability * false : Used for testing - return false for future calls to this function * 'reset': Used for testing - restore default behavior of this function */ function _wp_can_use_pcre_u( $set = null ) { static $utf8_pcre = 'reset'; if ( null !== $set ) { $utf8_pcre = $set; } if ( 'reset' === $utf8_pcre ) { $utf8_pcre = @preg_match( '/^./u', 'a' ); } return $utf8_pcre; } if ( ! function_exists( 'mb_substr' ) ) : /** * Compat function to mimic mb_substr(). * * @ignore * @since 3.2.0 * * @see _mb_substr() * * @param string $str The string to extract the substring from. * @param int $start Position to being extraction from in `$str`. * @param int|null $length Optional. Maximum number of characters to extract from `$str`. * Default null. * @param string|null $encoding Optional. Character encoding to use. Default null. * @return string Extracted substring. */ function mb_substr( $str, $start, $length = null, $encoding = null ) { return _mb_substr( $str, $start, $length, $encoding ); } endif; /** * Internal compat function to mimic mb_substr(). * * Only understands UTF-8 and 8bit. All other character sets will be treated as 8bit. * For $encoding === UTF-8, the $str input is expected to be a valid UTF-8 byte sequence. * The behavior of this function for invalid inputs is undefined. * * @ignore * @since 3.2.0 * * @param string $str The string to extract the substring from. * @param int $start Position to being extraction from in `$str`. * @param int|null $length Optional. Maximum number of characters to extract from `$str`. * Default null. * @param string|null $encoding Optional. Character encoding to use. Default null. * @return string Extracted substring. */ function _mb_substr( $str, $start, $length = null, $encoding = null ) { if ( null === $encoding ) { $encoding = get_option( 'blog_charset' ); } /* * The solution below works only for UTF-8, so in case of a different * charset just use built-in substr(). */ if ( ! in_array( $encoding, array( 'utf8', 'utf-8', 'UTF8', 'UTF-8' ) ) ) { return is_null( $length ) ? substr( $str, $start ) : substr( $str, $start, $length ); } if ( _wp_can_use_pcre_u() ) { // Use the regex unicode support to separate the UTF-8 characters into an array. preg_match_all( '/./us', $str, $match ); $chars = is_null( $length ) ? array_slice( $match[0], $start ) : array_slice( $match[0], $start, $length ); return implode( '', $chars ); } $regex = '/( [\x00-\x7F] # single-byte sequences 0xxxxxxx | [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx | \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2 | [\xE1-\xEC][\x80-\xBF]{2} | \xED[\x80-\x9F][\x80-\xBF] | [\xEE-\xEF][\x80-\xBF]{2} | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3 | [\xF1-\xF3][\x80-\xBF]{3} | \xF4[\x80-\x8F][\x80-\xBF]{2} )/x'; // Start with 1 element instead of 0 since the first thing we do is pop. $chars = array( '' ); do { // We had some string left over from the last round, but we counted it in that last round. array_pop( $chars ); /* * Split by UTF-8 character, limit to 1000 characters (last array element will contain * the rest of the string). */ $pieces = preg_split( $regex, $str, 1000, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); $chars = array_merge( $chars, $pieces ); // If there's anything left over, repeat the loop. } while ( count( $pieces ) > 1 && $str = array_pop( $pieces ) ); return join( '', array_slice( $chars, $start, $length ) ); } if ( ! function_exists( 'mb_strlen' ) ) : /** * Compat function to mimic mb_strlen(). * * @ignore * @since 4.2.0 * * @see _mb_strlen() * * @param string $str The string to retrieve the character length from. * @param string|null $encoding Optional. Character encoding to use. Default null. * @return int String length of `$str`. */ function mb_strlen( $str, $encoding = null ) { return _mb_strlen( $str, $encoding ); } endif; /** * Internal compat function to mimic mb_strlen(). * * Only understands UTF-8 and 8bit. All other character sets will be treated as 8bit. * For $encoding === UTF-8, the `$str` input is expected to be a valid UTF-8 byte * sequence. The behavior of this function for invalid inputs is undefined. * * @ignore * @since 4.2.0 * * @param string $str The string to retrieve the character length from. * @param string|null $encoding Optional. Character encoding to use. Default null. * @return int String length of `$str`. */ function _mb_strlen( $str, $encoding = null ) { if ( null === $encoding ) { $encoding = get_option( 'blog_charset' ); } /* * The solution below works only for UTF-8, so in case of a different charset * just use built-in strlen(). */ if ( ! in_array( $encoding, array( 'utf8', 'utf-8', 'UTF8', 'UTF-8' ) ) ) { return strlen( $str ); } if ( _wp_can_use_pcre_u() ) { // Use the regex unicode support to separate the UTF-8 characters into an array. preg_match_all( '/./us', $str, $match ); return count( $match[0] ); } $regex = '/(?: [\x00-\x7F] # single-byte sequences 0xxxxxxx | [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx | \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2 | [\xE1-\xEC][\x80-\xBF]{2} | \xED[\x80-\x9F][\x80-\xBF] | [\xEE-\xEF][\x80-\xBF]{2} | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3 | [\xF1-\xF3][\x80-\xBF]{3} | \xF4[\x80-\x8F][\x80-\xBF]{2} )/x'; // Start at 1 instead of 0 since the first thing we do is decrement. $count = 1; do { // We had some string left over from the last round, but we counted it in that last round. $count--; /* * Split by UTF-8 character, limit to 1000 characters (last array element will contain * the rest of the string). */ $pieces = preg_split( $regex, $str, 1000 ); // Increment. $count += count( $pieces ); // If there's anything left over, repeat the loop. } while ( $str = array_pop( $pieces ) ); // Fencepost: preg_split() always returns one extra item in the array. return --$count; } if ( ! function_exists( 'hash_hmac' ) ) : /** * Compat function to mimic hash_hmac(). * * @ignore * @since 3.2.0 * * @see _hash_hmac() * * @param string $algo Hash algorithm. Accepts 'md5' or 'sha1'. * @param string $data Data to be hashed. * @param string $key Secret key to use for generating the hash. * @param bool $raw_output Optional. Whether to output raw binary data (true), * or lowercase hexits (false). Default false. * @return string|false The hash in output determined by `$raw_output`. False if `$algo` * is unknown or invalid. */ function hash_hmac( $algo, $data, $key, $raw_output = false ) { return _hash_hmac( $algo, $data, $key, $raw_output ); } endif; /** * Internal compat function to mimic hash_hmac(). * * @ignore * @since 3.2.0 * * @param string $algo Hash algorithm. Accepts 'md5' or 'sha1'. * @param string $data Data to be hashed. * @param string $key Secret key to use for generating the hash. * @param bool $raw_output Optional. Whether to output raw binary data (true), * or lowercase hexits (false). Default false. * @return string|false The hash in output determined by `$raw_output`. False if `$algo` * is unknown or invalid. */ function _hash_hmac( $algo, $data, $key, $raw_output = false ) { $packs = array( 'md5' => 'H32', 'sha1' => 'H40', ); if ( ! isset( $packs[ $algo ] ) ) { return false; } $pack = $packs[ $algo ]; if ( strlen( $key ) > 64 ) { $key = pack( $pack, $algo( $key ) ); } $key = str_pad( $key, 64, chr( 0 ) ); $ipad = ( substr( $key, 0, 64 ) ^ str_repeat( chr( 0x36 ), 64 ) ); $opad = ( substr( $key, 0, 64 ) ^ str_repeat( chr( 0x5C ), 64 ) ); $hmac = $algo( $opad . pack( $pack, $algo( $ipad . $data ) ) ); if ( $raw_output ) { return pack( $pack, $hmac ); } return $hmac; } if ( ! function_exists( 'json_encode' ) ) { function json_encode( $string ) { global $wp_json; if ( ! ( $wp_json instanceof Services_JSON ) ) { require_once( ABSPATH . WPINC . '/class-json.php' ); $wp_json = new Services_JSON(); } return $wp_json->encodeUnsafe( $string ); } } if ( ! function_exists( 'json_decode' ) ) { /** * @global Services_JSON $wp_json * @param string $string * @param bool $assoc_array * @return object|array */ function json_decode( $string, $assoc_array = false ) { global $wp_json; if ( ! ( $wp_json instanceof Services_JSON ) ) { require_once( ABSPATH . WPINC . '/class-json.php' ); $wp_json = new Services_JSON(); } $res = $wp_json->decode( $string ); if ( $assoc_array ) { $res = _json_decode_object_helper( $res ); } return $res; } /** * @param object $data * @return array */ function _json_decode_object_helper( $data ) { if ( is_object( $data ) ) { $data = get_object_vars( $data ); } return is_array( $data ) ? array_map( __FUNCTION__, $data ) : $data; } } if ( ! function_exists( 'hash_equals' ) ) : /** * Timing attack safe string comparison * * Compares two strings using the same time whether they're equal or not. * * This function was added in PHP 5.6. * * Note: It can leak the length of a string when arguments of differing length are supplied. * * @since 3.9.2 * * @param string $a Expected string. * @param string $b Actual, user supplied, string. * @return bool Whether strings are equal. */ function hash_equals( $a, $b ) { $a_length = strlen( $a ); if ( $a_length !== strlen( $b ) ) { return false; } $result = 0; // Do not attempt to "optimize" this. for ( $i = 0; $i < $a_length; $i++ ) { $result |= ord( $a[ $i ] ) ^ ord( $b[ $i ] ); } return $result === 0; } endif; // JSON_PRETTY_PRINT was introduced in PHP 5.4 // Defined here to prevent a notice when using it with wp_json_encode() if ( ! defined( 'JSON_PRETTY_PRINT' ) ) { define( 'JSON_PRETTY_PRINT', 128 ); } if ( ! function_exists( 'json_last_error_msg' ) ) : /** * Retrieves the error string of the last json_encode() or json_decode() call. * * @since 4.4.0 * * @internal This is a compatibility function for PHP <5.5 * * @return bool|string Returns the error message on success, "No Error" if no error has occurred, * or false on failure. */ function json_last_error_msg() { // See https://core.trac.wordpress.org/ticket/27799. if ( ! function_exists( 'json_last_error' ) ) { return false; } $last_error_code = json_last_error(); // Just in case JSON_ERROR_NONE is not defined. $error_code_none = defined( 'JSON_ERROR_NONE' ) ? JSON_ERROR_NONE : 0; switch ( true ) { case $last_error_code === $error_code_none: return 'No error'; case defined( 'JSON_ERROR_DEPTH' ) && JSON_ERROR_DEPTH === $last_error_code: return 'Maximum stack depth exceeded'; case defined( 'JSON_ERROR_STATE_MISMATCH' ) && JSON_ERROR_STATE_MISMATCH === $last_error_code: return 'State mismatch (invalid or malformed JSON)'; case defined( 'JSON_ERROR_CTRL_CHAR' ) && JSON_ERROR_CTRL_CHAR === $last_error_code: return 'Control character error, possibly incorrectly encoded'; case defined( 'JSON_ERROR_SYNTAX' ) && JSON_ERROR_SYNTAX === $last_error_code: return 'Syntax error'; case defined( 'JSON_ERROR_UTF8' ) && JSON_ERROR_UTF8 === $last_error_code: return 'Malformed UTF-8 characters, possibly incorrectly encoded'; case defined( 'JSON_ERROR_RECURSION' ) && JSON_ERROR_RECURSION === $last_error_code: return 'Recursion detected'; case defined( 'JSON_ERROR_INF_OR_NAN' ) && JSON_ERROR_INF_OR_NAN === $last_error_code: return 'Inf and NaN cannot be JSON encoded'; case defined( 'JSON_ERROR_UNSUPPORTED_TYPE' ) && JSON_ERROR_UNSUPPORTED_TYPE === $last_error_code: return 'Type is not supported'; default: return 'An unknown error occurred'; } } endif; if ( ! interface_exists( 'JsonSerializable' ) ) { define( 'WP_JSON_SERIALIZE_COMPATIBLE', true ); /** * JsonSerializable interface. * * Compatibility shim for PHP <5.4 * * @link https://secure.php.net/jsonserializable * * @since 4.4.0 */ interface JsonSerializable { public function jsonSerialize(); } } // random_int was introduced in PHP 7.0 if ( ! function_exists( 'random_int' ) ) { require ABSPATH . WPINC . '/random_compat/random.php'; } if ( ! function_exists( 'array_replace_recursive' ) ) : /** * PHP-agnostic version of {@link array_replace_recursive()}. * * The array_replace_recursive() function is a PHP 5.3 function. WordPress * currently supports down to PHP 5.2, so this method is a workaround * for PHP 5.2. * * Note: array_replace_recursive() supports infinite arguments, but for our use- * case, we only need to support two arguments. * * Subject to removal once WordPress makes PHP 5.3.0 the minimum requirement. * * @since 4.5.3 * * @see https://secure.php.net/manual/en/function.array-replace-recursive.php#109390 * * @param array $base Array with keys needing to be replaced. * @param array $replacements Array with the replaced keys. * * @return array */ function array_replace_recursive( $base = array(), $replacements = array() ) { foreach ( array_slice( func_get_args(), 1 ) as $replacements ) { $bref_stack = array( &$base ); $head_stack = array( $replacements ); do { end( $bref_stack ); $bref = &$bref_stack[ key( $bref_stack ) ]; $head = array_pop( $head_stack ); unset( $bref_stack[ key( $bref_stack ) ] ); foreach ( array_keys( $head ) as $key ) { if ( isset( $key, $bref ) && isset( $bref[ $key ] ) && is_array( $bref[ $key ] ) && isset( $head[ $key ] ) && is_array( $head[ $key ] ) ) { $bref_stack[] = &$bref[ $key ]; $head_stack[] = $head[ $key ]; } else { $bref[ $key ] = $head[ $key ]; } } } while ( count( $head_stack ) ); } return $base; } endif; /** * Polyfill for the SPL autoloader. In PHP 5.2 (but not 5.3 and later), SPL can * be disabled, and PHP 7.2 raises notices if the compiler finds an __autoload() * function declaration. Function availability is checked here, and the * autoloader is included only if necessary. */ if ( ! function_exists( 'spl_autoload_register' ) ) { require_once ABSPATH . WPINC . '/spl-autoload-compat.php'; } if ( ! function_exists( 'is_countable' ) ) { /** * Polyfill for is_countable() function added in PHP 7.3. * * Verify that the content of a variable is an array or an object * implementing the Countable interface. * * @since 4.9.6 * * @param mixed $var The value to check. * * @return bool True if `$var` is countable, false otherwise. */ function is_countable( $var ) { return ( is_array( $var ) || $var instanceof Countable || $var instanceof SimpleXMLElement || $var instanceof ResourceBundle ); } } if ( ! function_exists( 'is_iterable' ) ) { /** * Polyfill for is_iterable() function added in PHP 7.1. * * Verify that the content of a variable is an array or an object * implementing the Traversable interface. * * @since 4.9.6 * * @param mixed $var The value to check. * * @return bool True if `$var` is iterable, false otherwise. */ function is_iterable( $var ) { return ( is_array( $var ) || $var instanceof Traversable ); } } if ( ! function_exists( 'str_starts_with' ) ) { /** * Polyfill for `str_starts_with()` function added in PHP 8.0. * * Performs a case-sensitive check indicating if * the haystack begins with needle. * * @since 5.9.0 * * @param string $haystack The string to search in. * @param string $needle The substring to search for in the `$haystack`. * @return bool True if `$haystack` starts with `$needle`, otherwise false. */ function str_starts_with( $haystack, $needle ) { if ( '' === $needle ) { return true; } return 0 === strpos( $haystack, $needle ); } } if ( ! function_exists( 'str_ends_with' ) ) { /** * Polyfill for `str_ends_with()` function added in PHP 8.0. * * Performs a case-sensitive check indicating if * the haystack ends with needle. * * @since 5.9.0 * * @param string $haystack The string to search in. * @param string $needle The substring to search for in the `$haystack`. * @return bool True if `$haystack` ends with `$needle`, otherwise false. */ function str_ends_with( $haystack, $needle ) { if ( '' === $haystack ) { return '' === $needle; } $len = strlen( $needle ); return substr( $haystack, -$len, $len ) === $needle; } } /** * Option API * * @package WordPress * @subpackage Option */ /** * Retrieves an option value based on an option name. * * If the option does not exist or does not have a value, then the return value * will be false. This is useful to check whether you need to install an option * and is commonly used during installation of plugin options and to test * whether upgrading is required. * * If the option was serialized then it will be unserialized when it is returned. * * Any scalar values will be returned as strings. You may coerce the return type of * a given option by registering an {@see 'option_$option'} filter callback. * * @since 1.5.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $option Name of option to retrieve. Expected to not be SQL-escaped. * @param mixed $default Optional. Default value to return if the option does not exist. * @return mixed Value set for the option. */ function get_option( $option, $default = false ) { global $wpdb; $option = trim( $option ); if ( empty( $option ) ) { return false; } /** * Filters the value of an existing option before it is retrieved. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * Passing a truthy value to the filter will short-circuit retrieving * the option value, returning the passed value instead. * * @since 1.5.0 * @since 4.4.0 The `$option` parameter was added. * @since 4.9.0 The `$default` parameter was added. * * @param bool|mixed $pre_option The value to return instead of the option value. This differs from * `$default`, 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 The fallback value to return if the option does not exist. * Default is false. */ $pre = apply_filters( "pre_option_{$option}", false, $option, $default ); 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() ) { // prevent non-existent options from triggering multiple queries $notoptions = wp_cache_get( 'notoptions', 'options' ); if ( 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 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, $option, $passed_default ); } $alloptions = wp_load_alloptions(); if ( isset( $alloptions[ $option ] ) ) { $value = $alloptions[ $option ]; } else { $value = wp_cache_get( $option, 'options' ); if ( false === $value ) { $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 if ( ! is_array( $notoptions ) ) { $notoptions = array(); } $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, $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, $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' ) ) ) { $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 ); } /** * Protect 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( __( '%s is a protected WP option and may not be modified' ), esc_html( $option ) ) ); } } /** * Print 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 * * @global wpdb $wpdb WordPress database abstraction object. * * @return array List of all options. */ function wp_load_alloptions() { global $wpdb; if ( ! wp_installing() || ! is_multisite() ) { $alloptions = wp_cache_get( 'alloptions', 'options' ); } else { $alloptions = false; } if ( ! $alloptions ) { $suppress = $wpdb->suppress_errors(); if ( ! $alloptions_db = $wpdb->get_results( "SELECT option_name, option_value FROM $wpdb->options WHERE autoload = 'yes'" ) ) { $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 ); } /** * Loads and caches certain often requested site options if is_multisite() and a persistent cache is not being used. * * @since 3.0.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $network_id Optional site ID for which to query the options. Defaults to the current site. */ function wp_load_core_site_options( $network_id = null ) { global $wpdb; if ( ! is_multisite() || wp_using_ext_object_cache() || wp_installing() ) { return; } if ( empty( $network_id ) ) { $network_id = get_current_network_id(); } $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' ); $core_options_in = "'" . implode( "', '", $core_options ) . "'"; $options = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM $wpdb->sitemeta WHERE meta_key IN ($core_options_in) AND site_id = %d", $network_id ) ); foreach ( $options as $option ) { $key = $option->meta_key; $cache_key = "{$network_id}:$key"; $option->meta_value = maybe_unserialize( $option->meta_value ); wp_cache_set( $cache_key, $option->meta_value, 'site-options' ); } } /** * Update 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 can not be serialized or added as an option. * * If the option does not exist, then the option will be added with the option value, * with an `$autoload` value of 'yes'. * * @since 1.0.0 * @since 4.2.0 The `$autoload` parameter was added. * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $option Option name. Expected to not be SQL-escaped. * @param mixed $value Option value. Must be serializable if non-scalar. Expected to not be SQL-escaped. * @param string|bool $autoload Optional. Whether to load the option when WordPress starts up. For existing options, * `$autoload` can only be updated using `update_option()` if `$value` is also changed. * Accepts 'yes'|true to enable or 'no'|false to disable. For non-existent options, * the default value is 'yes'. Default null. * @return bool False if value was not updated and true if value was updated. */ function update_option( $option, $value, $autoload = null ) { global $wpdb; $option = trim( $option ); if ( empty( $option ) ) { return false; } 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 ) { // Default setting for new options is 'yes'. if ( null === $autoload ) { $autoload = 'yes'; } 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'] = ( 'no' === $autoload || false === $autoload ) ? 'no' : 'yes'; } $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() ) { $alloptions = wp_load_alloptions(); if ( isset( $alloptions[ $option ] ) ) { $alloptions[ $option ] = $serialized_value; wp_cache_set( 'alloptions', $alloptions, 'options' ); } else { 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; } /** * Add 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 can not 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 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $option Name of 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 string|bool $autoload Optional. Whether to load the option when WordPress starts up. * Default is enabled. Accepts 'no' to disable for legacy reasons. * @return bool False if option was not added and true if option was added. */ function add_option( $option, $value = '', $deprecated = '', $autoload = 'yes' ) { global $wpdb; if ( ! empty( $deprecated ) ) { _deprecated_argument( __FUNCTION__, '2.3.0' ); } $option = trim( $option ); if ( empty( $option ) ) { return false; } 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 = ( 'no' === $autoload || false === $autoload ) ? 'no' : 'yes'; /** * 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 ( 'yes' == $autoload ) { $alloptions = wp_load_alloptions(); $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 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 option to remove. Expected to not be SQL-escaped. * @return bool True, if option is successfully deleted. False on failure. */ function delete_option( $option ) { global $wpdb; $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 ( 'yes' == $row->autoload ) { $alloptions = wp_load_alloptions(); if ( is_array( $alloptions ) && isset( $alloptions[ $option ] ) ) { unset( $alloptions[ $option ] ); wp_cache_set( 'alloptions', $alloptions, 'options' ); } } else { wp_cache_delete( $option, '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; } /** * Delete a transient. * * @since 2.8.0 * * @param string $transient Transient name. Expected to not be SQL-escaped. * @return bool true if successful, 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() ) { $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; } /** * Get 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. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * Passing a truthy value to the filter will effectively short-circuit retrieval * of the transient, returning the passed 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 the returned 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() ) { $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; $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 ); } /** * Set/update 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 False if value was not set and true if value was set. */ 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() ) { $result = wp_cache_set( $transient, $value, 'transient', $expiration ); } else { $transient_timeout = '_transient_timeout_' . $transient; $transient_option = '_transient_' . $transient; if ( false === get_option( $transient_option ) ) { $autoload = 'yes'; if ( $expiration ) { $autoload = 'no'; add_option( $transient_timeout, time() + $expiration, '', 'no' ); } $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, '', 'no' ); $result = add_option( $transient_option, $value, '', 'no' ); $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. * * The multi-table delete syntax is used to delete the transient record * from table a, and the corresponding transient_timeout record from table b. * * @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() ) { // non-Multisite 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; } if ( ! $user_id = get_current_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, null, $secure ); setcookie( 'wp-settings-time-' . $user_id, time(), time() + YEAR_IN_SECONDS, SITECOOKIEPATH, null, $secure ); $_COOKIE[ 'wp-settings-' . $user_id ] = $settings; } /** * Retrieve user interface setting value based on setting name. * * @since 2.7.0 * * @param string $name The name of the setting. * @param string $default Optional default value to return when $name is not set. * @return mixed the last saved user setting or the default value/false if it doesn't exist. */ function get_user_setting( $name, $default = false ) { $all_user_settings = get_all_user_settings(); return isset( $all_user_settings[ $name ] ) ? $all_user_settings[ $name ] : $default; } /** * Add or update user interface setting. * * Both $name and $value can contain only ASCII letters, numbers 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 if not. Null if the current user can't be established. */ 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 ); } /** * Delete 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 if not. Null if the current user can't be established. */ 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; } /** * Retrieve 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; if ( ! $user_id = get_current_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. Set all user interface settings. * * @since 2.8.0 * @access private * * @global array $_updated_user_settings * * @param array $user_settings User settings. * @return bool|null False if the current user can't be found, null if the current * user is not a super admin or a member of the site, otherwise true. */ function wp_set_all_user_settings( $user_settings ) { global $_updated_user_settings; if ( ! $user_id = get_current_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; } /** * Delete the user settings of the current user. * * @since 2.7.0 */ function delete_all_user_settings() { if ( ! $user_id = get_current_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 option to retrieve. Expected to not be SQL-escaped. * @param mixed $default Optional value to return if 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 = false, $deprecated = true ) { return get_network_option( null, $option, $default ); } /** * Add 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 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 False if the option was not added. True if the option was added. */ function add_site_option( $option, $value ) { return add_network_option( null, $option, $value ); } /** * Removes a 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 option to remove. Expected to not be SQL-escaped. * @return bool True, if succeed. False, if failure. */ function delete_site_option( $option ) { return delete_network_option( null, $option ); } /** * Update 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 option. Expected to not be SQL-escaped. * @param mixed $value Option value. Expected to not be SQL-escaped. * @return bool False if value was not updated. True if value was updated. */ function update_site_option( $option, $value ) { return update_network_option( null, $option, $value ); } /** * Retrieve 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 option to retrieve. Expected to not be SQL-escaped. * @param mixed $default 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 = 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 an existing network option before it is retrieved. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * Passing a truthy value to the filter will effectively short-circuit retrieval, * returning the passed 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` parameter was added. * * @param mixed $pre_option The value to return instead of the option value. This differs from * `$default`, which is used as the fallback value in the event the * option doesn't exist elsewhere in get_network_option(). Default * is false (to skip past the short-circuit). * @param string $option Option name. * @param int $network_id ID of the network. * @param mixed $default The fallback value to return if the option does not exist. * Default is false. */ $pre = apply_filters( "pre_site_option_{$option}", false, $option, $network_id, $default ); 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 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 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, $option, $network_id ); } if ( ! is_multisite() ) { /** This filter is documented in wp-includes/option.php */ $default = apply_filters( 'default_site_option_' . $option, $default, $option, $network_id ); $value = get_option( $option, $default ); } 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, $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 ); } /** * Add 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 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 False if option was not added and true if option was added. */ 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, '', 'no' ); } 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 option to remove. Expected to not be SQL-escaped. * @return bool True, if succeed. False, if failure. */ 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 ) { /** * 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; } /** * Update 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 option. Expected to not be SQL-escaped. * @param mixed $value Option value. Expected to not be SQL-escaped. * @return bool False if value was not updated and true if value was updated. */ 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, false ); /** * 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, 'no' ); } 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; } /** * Delete a site transient. * * @since 2.9.0 * * @param string $transient Transient name. Expected to not be SQL-escaped. * @return bool True if successful, 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() ) { $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; } /** * Get 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. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * Passing a truthy value to the filter will effectively short-circuit retrieval, * returning the passed 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 the returned 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() ) { $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 ) ) { $transient_timeout = '_site_transient_timeout_' . $transient; $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 ); } /** * Set/update the value of a site transient. * * You do not need to serialize values, if the value needs to be serialize, 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 False if value was not set and true if value was set. */ 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() ) { $result = wp_cache_set( $transient, $value, 'site-transient', $expiration ); } else { $transient_timeout = '_site_transient_timeout_' . $transient; $option = '_site_transient_' . $transient; 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; } /** * Register 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 */ function register_initial_settings() { register_setting( 'general', 'blogname', array( 'show_in_rest' => array( 'name' => 'title', ), 'type' => 'string', 'description' => __( 'Site title.' ), ) ); register_setting( 'general', 'blogdescription', array( 'show_in_rest' => array( 'name' => 'description', ), 'type' => 'string', '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', 'description' => __( 'Blog pages show at most.' ), 'default' => 10, ) ); 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', 'description' => __( 'Allow people to post comments on new articles.' ), ) ); } /** * Register a setting and its data. * * @since 2.7.0 * @since 4.7.0 `$args` can be passed to set flags on the setting, similar to `register_meta()`. * * @global array $new_whitelist_options * @global array $wp_registered_settings * * @param string $option_group A settings group name. Should correspond to a whitelisted option key name. * Default whitelisted option key names include "general," "discussion," and "reading," among others. * @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', and 'number'. * @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 $show_in_rest Whether data associated with this setting should be included in the REST API. * @type mixed $default Default value when calling `get_option()`. * } */ function register_setting( $option_group, $option_name, $args = array() ) { global $new_whitelist_options, $wp_registered_settings; $defaults = array( 'type' => 'string', 'group' => $option_group, '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 ); if ( ! is_array( $wp_registered_settings ) ) { $wp_registered_settings = array(); } if ( 'misc' == $option_group ) { _deprecated_argument( __FUNCTION__, '3.0.0', /* translators: %s: misc */ sprintf( __( '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', /* translators: %s: privacy */ sprintf( __( 'The "%s" options group has been removed. Use another settings group.' ), 'privacy' ) ); $option_group = 'reading'; } $new_whitelist_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 ); } $wp_registered_settings[ $option_name ] = $args; } /** * Unregister a setting. * * @since 2.7.0 * @since 4.7.0 `$sanitize_callback` was deprecated. The callback from `register_setting()` is now used instead. * * @global array $new_whitelist_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 Deprecated. */ function unregister_setting( $option_group, $option_name, $deprecated = '' ) { global $new_whitelist_options, $wp_registered_settings; if ( 'misc' == $option_group ) { _deprecated_argument( __FUNCTION__, '3.0.0', /* translators: %s: misc */ sprintf( __( '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', /* translators: %s: privacy */ sprintf( __( 'The "%s" options group has been removed. Use another settings group.' ), 'privacy' ) ); $option_group = 'reading'; } $pos = array_search( $option_name, (array) $new_whitelist_options[ $option_group ] ); if ( $pos !== false ) { unset( $new_whitelist_options[ $option_group ][ $pos ] ); } if ( '' !== $deprecated ) { _deprecated_argument( __FUNCTION__, '4.7.0', /* translators: 1: $sanitize_callback, 2: register_setting() */ sprintf( __( '%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 ); } 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; } /** * Filter 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 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, $option, $passed_default ) { if ( $passed_default ) { return $default; } $registered = get_registered_settings(); if ( empty( $registered[ $option ] ) ) { return $default; } return $registered[ $option ]['default']; } /** * Core Translation API * * @package WordPress * @subpackage i18n * @since 1.2.0 */ /** * Retrieves the current locale. * * If the locale is set, then it will filter the locale in the {@see 'locale'} * filter hook and return the value. * * If the locale is not set already, then the WPLANG constant is used if it is * defined. Then it is filtered through the {@see 'locale'} filter hook and * the value for the locale global set and the locale is returned. * * The process to get the locale should only be done once, but the locale will * always be filtered using the {@see 'locale'} hook. * * @since 1.5.0 * * @global string $locale * @global string $wp_local_package * * @return string The locale of the blog or from the {@see 'locale'} hook. */ function get_locale() { global $locale, $wp_local_package; if ( isset( $locale ) ) { /** * Filters the locale ID of the WordPress installation. * * @since 1.5.0 * * @param string $locale The locale ID. */ return apply_filters( 'locale', $locale ); } if ( isset( $wp_local_package ) ) { $locale = $wp_local_package; } // WPLANG was defined in wp-config. if ( defined( 'WPLANG' ) ) { $locale = WPLANG; } // If multisite, check options. if ( is_multisite() ) { // Don't check blog option when installing. if ( wp_installing() || ( false === $ms_locale = get_option( 'WPLANG' ) ) ) { $ms_locale = get_site_option( 'WPLANG' ); } if ( $ms_locale !== false ) { $locale = $ms_locale; } } else { $db_locale = get_option( 'WPLANG' ); if ( $db_locale !== false ) { $locale = $db_locale; } } if ( empty( $locale ) ) { $locale = 'en_US'; } /** This filter is documented in wp-includes/l10n.php */ return apply_filters( 'locale', $locale ); } /** * Retrieves the locale of a user. * * If the user has a locale set to a non-empty string then it will be * returned. Otherwise it returns the locale of get_locale(). * * @since 4.7.0 * * @param int|WP_User $user_id User's ID or a WP_User object. Defaults to current user. * @return string The locale of the user. */ function get_user_locale( $user_id = 0 ) { $user = false; if ( 0 === $user_id && function_exists( 'wp_get_current_user' ) ) { $user = wp_get_current_user(); } elseif ( $user_id instanceof WP_User ) { $user = $user_id; } elseif ( $user_id && is_numeric( $user_id ) ) { $user = get_user_by( 'id', $user_id ); } if ( ! $user ) { return get_locale(); } $locale = $user->locale; return $locale ? $locale : get_locale(); } /** * Determine the current locale desired for the request. * * @since 5.0.0 * * @global string $pagenow * * @return string The determined locale. */ function determine_locale() { /** * Filters the locale for the current request prior to the default determination process. * * Using this filter allows to override the default logic, effectively short-circuiting the function. * * @since 5.0.0 * * @param string|null The locale to return and short-circuit, or null as default. */ $determined_locale = apply_filters( 'pre_determine_locale', null ); if ( ! empty( $determined_locale ) && is_string( $determined_locale ) ) { return $determined_locale; } $determined_locale = get_locale(); if ( is_admin() ) { $determined_locale = get_user_locale(); } if ( isset( $_GET['_locale'] ) && 'user' === $_GET['_locale'] && wp_is_json_request() ) { $determined_locale = get_user_locale(); } if ( ! empty( $_GET['wp_lang'] ) && ! empty( $GLOBALS['pagenow'] ) && 'wp-login.php' === $GLOBALS['pagenow'] ) { $determined_locale = sanitize_locale_name( wp_unslash( $_GET['wp_lang'] ) ); } /** * Filters the locale for the current request. * * @since 5.0.0 * * @param string $locale The locale. */ return apply_filters( 'determine_locale', $determined_locale ); } /** * Retrieve the translation of $text. * * If there is no translation, or the text domain isn't loaded, the original text is returned. * * *Note:* Don't use translate() directly, use __() or related functions. * * @since 2.2.0 * * @param string $text Text to translate. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string Translated text */ function translate( $text, $domain = 'default' ) { $translations = get_translations_for_domain( $domain ); $translation = $translations->translate( $text ); /** * Filters text with its translation. * * @since 2.0.11 * * @param string $translation Translated text. * @param string $text Text to translate. * @param string $domain Text domain. Unique identifier for retrieving translated strings. */ return apply_filters( 'gettext', $translation, $text, $domain ); } /** * Remove last item on a pipe-delimited string. * * Meant for removing the last item in a string, such as 'Role name|User role'. The original * string will be returned if no pipe '|' characters are found in the string. * * @since 2.8.0 * * @param string $string A pipe-delimited string. * @return string Either $string or everything before the last pipe. */ function before_last_bar( $string ) { $last_bar = strrpos( $string, '|' ); if ( false === $last_bar ) { return $string; } else { return substr( $string, 0, $last_bar ); } } /** * Retrieve the translation of $text in the context defined in $context. * * If there is no translation, or the text domain isn't loaded the original * text is returned. * * *Note:* Don't use translate_with_gettext_context() directly, use _x() or related functions. * * @since 2.8.0 * * @param string $text Text to translate. * @param string $context Context information for the translators. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string Translated text on success, original text on failure. */ function translate_with_gettext_context( $text, $context, $domain = 'default' ) { $translations = get_translations_for_domain( $domain ); $translation = $translations->translate( $text, $context ); /** * Filters text with its translation based on context information. * * @since 2.8.0 * * @param string $translation Translated text. * @param string $text Text to translate. * @param string $context Context information for the translators. * @param string $domain Text domain. Unique identifier for retrieving translated strings. */ return apply_filters( 'gettext_with_context', $translation, $text, $context, $domain ); } /** * Retrieve the translation of $text. * * If there is no translation, or the text domain isn't loaded, the original text is returned. * * @since 2.1.0 * * @param string $text Text to translate. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string Translated text. */ function __( $text, $domain = 'default' ) { return translate( $text, $domain ); } /** * Retrieve the translation of $text and escapes it for safe use in an attribute. * * If there is no translation, or the text domain isn't loaded, the original text is returned. * * @since 2.8.0 * * @param string $text Text to translate. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string Translated text on success, original text on failure. */ function esc_attr__( $text, $domain = 'default' ) { return esc_attr( translate( $text, $domain ) ); } /** * Retrieve the translation of $text and escapes it for safe use in HTML output. * * If there is no translation, or the text domain isn't loaded, the original text * is escaped and returned. * * @since 2.8.0 * * @param string $text Text to translate. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string Translated text */ function esc_html__( $text, $domain = 'default' ) { return esc_html( translate( $text, $domain ) ); } /** * Display translated text. * * @since 1.2.0 * * @param string $text Text to translate. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. */ function _e( $text, $domain = 'default' ) { echo translate( $text, $domain ); } /** * Display translated text that has been escaped for safe use in an attribute. * * @since 2.8.0 * * @param string $text Text to translate. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. */ function esc_attr_e( $text, $domain = 'default' ) { echo esc_attr( translate( $text, $domain ) ); } /** * Display translated text that has been escaped for safe use in HTML output. * * @since 2.8.0 * * @param string $text Text to translate. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. */ function esc_html_e( $text, $domain = 'default' ) { echo esc_html( translate( $text, $domain ) ); } /** * Retrieve translated string with gettext context. * * Quite a few times, there will be collisions with similar translatable text * found in more than two places, but with different translated context. * * By including the context in the pot file, translators can translate the two * strings differently. * * @since 2.8.0 * * @param string $text Text to translate. * @param string $context Context information for the translators. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string Translated context string without pipe. */ function _x( $text, $context, $domain = 'default' ) { return translate_with_gettext_context( $text, $context, $domain ); } /** * Display translated string with gettext context. * * @since 3.0.0 * * @param string $text Text to translate. * @param string $context Context information for the translators. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string Translated context string without pipe. */ function _ex( $text, $context, $domain = 'default' ) { echo _x( $text, $context, $domain ); } /** * Translate string with gettext context, and escapes it for safe use in an attribute. * * @since 2.8.0 * * @param string $text Text to translate. * @param string $context Context information for the translators. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string Translated text */ function esc_attr_x( $text, $context, $domain = 'default' ) { return esc_attr( translate_with_gettext_context( $text, $context, $domain ) ); } /** * Translate string with gettext context, and escapes it for safe use in HTML output. * * @since 2.9.0 * * @param string $text Text to translate. * @param string $context Context information for the translators. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string Translated text. */ function esc_html_x( $text, $context, $domain = 'default' ) { return esc_html( translate_with_gettext_context( $text, $context, $domain ) ); } /** * Translates and retrieves the singular or plural form based on the supplied number. * * Used when you want to use the appropriate form of a string based on whether a * number is singular or plural. * * Example: * * printf( _n( '%s person', '%s people', $count, 'text-domain' ), number_format_i18n( $count ) ); * * @since 2.8.0 * * @param string $single The text to be used if the number is singular. * @param string $plural The text to be used if the number is plural. * @param int $number The number to compare against to use either the singular or plural form. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string The translated singular or plural form. */ function _n( $single, $plural, $number, $domain = 'default' ) { $translations = get_translations_for_domain( $domain ); $translation = $translations->translate_plural( $single, $plural, $number ); /** * Filters the singular or plural form of a string. * * @since 2.2.0 * * @param string $translation Translated text. * @param string $single The text to be used if the number is singular. * @param string $plural The text to be used if the number is plural. * @param string $number The number to compare against to use either the singular or plural form. * @param string $domain Text domain. Unique identifier for retrieving translated strings. */ return apply_filters( 'ngettext', $translation, $single, $plural, $number, $domain ); } /** * Translates and retrieves the singular or plural form based on the supplied number, with gettext context. * * This is a hybrid of _n() and _x(). It supports context and plurals. * * Used when you want to use the appropriate form of a string with context based on whether a * number is singular or plural. * * Example of a generic phrase which is disambiguated via the context parameter: * * printf( _nx( '%s group', '%s groups', $people, 'group of people', 'text-domain' ), number_format_i18n( $people ) ); * printf( _nx( '%s group', '%s groups', $animals, 'group of animals', 'text-domain' ), number_format_i18n( $animals ) ); * * @since 2.8.0 * * @param string $single The text to be used if the number is singular. * @param string $plural The text to be used if the number is plural. * @param int $number The number to compare against to use either the singular or plural form. * @param string $context Context information for the translators. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default 'default'. * @return string The translated singular or plural form. */ function _nx( $single, $plural, $number, $context, $domain = 'default' ) { $translations = get_translations_for_domain( $domain ); $translation = $translations->translate_plural( $single, $plural, $number, $context ); /** * Filters the singular or plural form of a string with gettext context. * * @since 2.8.0 * * @param string $translation Translated text. * @param string $single The text to be used if the number is singular. * @param string $plural The text to be used if the number is plural. * @param string $number The number to compare against to use either the singular or plural form. * @param string $context Context information for the translators. * @param string $domain Text domain. Unique identifier for retrieving translated strings. */ return apply_filters( 'ngettext_with_context', $translation, $single, $plural, $number, $context, $domain ); } /** * Registers plural strings in POT file, but does not translate them. * * Used when you want to keep structures with translatable plural * strings and use them later when the number is known. * * Example: * * $message = _n_noop( '%s post', '%s posts', 'text-domain' ); * ... * printf( translate_nooped_plural( $message, $count, 'text-domain' ), number_format_i18n( $count ) ); * * @since 2.5.0 * * @param string $singular Singular form to be localized. * @param string $plural Plural form to be localized. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default null. * @return array { * Array of translation information for the strings. * * @type string $0 Singular form to be localized. No longer used. * @type string $1 Plural form to be localized. No longer used. * @type string $singular Singular form to be localized. * @type string $plural Plural form to be localized. * @type null $context Context information for the translators. * @type string $domain Text domain. * } */ function _n_noop( $singular, $plural, $domain = null ) { return array( 0 => $singular, 1 => $plural, 'singular' => $singular, 'plural' => $plural, 'context' => null, 'domain' => $domain, ); } /** * Registers plural strings with gettext context in POT file, but does not translate them. * * Used when you want to keep structures with translatable plural * strings and use them later when the number is known. * * Example of a generic phrase which is disambiguated via the context parameter: * * $messages = array( * 'people' => _nx_noop( '%s group', '%s groups', 'people', 'text-domain' ), * 'animals' => _nx_noop( '%s group', '%s groups', 'animals', 'text-domain' ), * ); * ... * $message = $messages[ $type ]; * printf( translate_nooped_plural( $message, $count, 'text-domain' ), number_format_i18n( $count ) ); * * @since 2.8.0 * * @param string $singular Singular form to be localized. * @param string $plural Plural form to be localized. * @param string $context Context information for the translators. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. * Default null. * @return array { * Array of translation information for the strings. * * @type string $0 Singular form to be localized. No longer used. * @type string $1 Plural form to be localized. No longer used. * @type string $2 Context information for the translators. No longer used. * @type string $singular Singular form to be localized. * @type string $plural Plural form to be localized. * @type string $context Context information for the translators. * @type string $domain Text domain. * } */ function _nx_noop( $singular, $plural, $context, $domain = null ) { return array( 0 => $singular, 1 => $plural, 2 => $context, 'singular' => $singular, 'plural' => $plural, 'context' => $context, 'domain' => $domain, ); } /** * Translates and retrieves the singular or plural form of a string that's been registered * with _n_noop() or _nx_noop(). * * Used when you want to use a translatable plural string once the number is known. * * Example: * * $message = _n_noop( '%s post', '%s posts', 'text-domain' ); * ... * printf( translate_nooped_plural( $message, $count, 'text-domain' ), number_format_i18n( $count ) ); * * @since 3.1.0 * * @param array $nooped_plural Array with singular, plural, and context keys, usually the result of _n_noop() or _nx_noop(). * @param int $count Number of objects. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. If $nooped_plural contains * a text domain passed to _n_noop() or _nx_noop(), it will override this value. Default 'default'. * @return string Either $single or $plural translated text. */ function translate_nooped_plural( $nooped_plural, $count, $domain = 'default' ) { if ( $nooped_plural['domain'] ) { $domain = $nooped_plural['domain']; } if ( $nooped_plural['context'] ) { return _nx( $nooped_plural['singular'], $nooped_plural['plural'], $count, $nooped_plural['context'], $domain ); } else { return _n( $nooped_plural['singular'], $nooped_plural['plural'], $count, $domain ); } } /** * Load a .mo file into the text domain $domain. * * If the text domain already exists, the translations will be merged. If both * sets have the same string, the translation from the original value will be taken. * * On success, the .mo file will be placed in the $l10n global by $domain * and will be a MO object. * * @since 1.5.0 * * @global MO[] $l10n An array of all currently loaded text domains. * @global MO[] $l10n_unloaded An array of all text domains that have been unloaded again. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @param string $mofile Path to the .mo file. * @return bool True on success, false on failure. */ function load_textdomain( $domain, $mofile ) { global $l10n, $l10n_unloaded; $l10n_unloaded = (array) $l10n_unloaded; /** * Filters whether to override the .mo file loading. * * @since 2.9.0 * * @param bool $override Whether to override the .mo file loading. Default false. * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @param string $mofile Path to the MO file. */ $plugin_override = apply_filters( 'override_load_textdomain', false, $domain, $mofile ); if ( true == $plugin_override ) { unset( $l10n_unloaded[ $domain ] ); return true; } /** * Fires before the MO translation file is loaded. * * @since 2.9.0 * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @param string $mofile Path to the .mo file. */ do_action( 'load_textdomain', $domain, $mofile ); /** * Filters MO file path for loading translations for a specific text domain. * * @since 2.9.0 * * @param string $mofile Path to the MO file. * @param string $domain Text domain. Unique identifier for retrieving translated strings. */ $mofile = apply_filters( 'load_textdomain_mofile', $mofile, $domain ); if ( ! is_readable( $mofile ) ) { return false; } $mo = new MO(); if ( ! $mo->import_from_file( $mofile ) ) { return false; } if ( isset( $l10n[ $domain ] ) ) { $mo->merge_with( $l10n[ $domain ] ); } unset( $l10n_unloaded[ $domain ] ); $l10n[ $domain ] = &$mo; return true; } /** * Unload translations for a text domain. * * @since 3.0.0 * * @global MO[] $l10n An array of all currently loaded text domains. * @global MO[] $l10n_unloaded An array of all text domains that have been unloaded again. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @return bool Whether textdomain was unloaded. */ function unload_textdomain( $domain ) { global $l10n, $l10n_unloaded; $l10n_unloaded = (array) $l10n_unloaded; /** * Filters whether to override the text domain unloading. * * @since 3.0.0 * * @param bool $override Whether to override the text domain unloading. Default false. * @param string $domain Text domain. Unique identifier for retrieving translated strings. */ $plugin_override = apply_filters( 'override_unload_textdomain', false, $domain ); if ( $plugin_override ) { $l10n_unloaded[ $domain ] = true; return true; } /** * Fires before the text domain is unloaded. * * @since 3.0.0 * * @param string $domain Text domain. Unique identifier for retrieving translated strings. */ do_action( 'unload_textdomain', $domain ); if ( isset( $l10n[ $domain ] ) ) { unset( $l10n[ $domain ] ); $l10n_unloaded[ $domain ] = true; return true; } return false; } /** * Load default translated strings based on locale. * * Loads the .mo file in WP_LANG_DIR constant path from WordPress root. * The translated (.mo) file is named based on the locale. * * @see load_textdomain() * * @since 1.5.0 * * @param string $locale Optional. Locale to load. Default is the value of get_locale(). * @return bool Whether the textdomain was loaded. */ function load_default_textdomain( $locale = null ) { if ( null === $locale ) { $locale = determine_locale(); } // Unload previously loaded strings so we can switch translations. unload_textdomain( 'default' ); $return = load_textdomain( 'default', WP_LANG_DIR . "/$locale.mo" ); if ( ( is_multisite() || ( defined( 'WP_INSTALLING_NETWORK' ) && WP_INSTALLING_NETWORK ) ) && ! file_exists( WP_LANG_DIR . "/admin-$locale.mo" ) ) { load_textdomain( 'default', WP_LANG_DIR . "/ms-$locale.mo" ); return $return; } if ( is_admin() || wp_installing() || ( defined( 'WP_REPAIRING' ) && WP_REPAIRING ) ) { load_textdomain( 'default', WP_LANG_DIR . "/admin-$locale.mo" ); } if ( is_network_admin() || ( defined( 'WP_INSTALLING_NETWORK' ) && WP_INSTALLING_NETWORK ) ) { load_textdomain( 'default', WP_LANG_DIR . "/admin-network-$locale.mo" ); } return $return; } /** * Loads a plugin's translated strings. * * If the path is not given then it will be the root of the plugin directory. * * The .mo file should be named based on the text domain with a dash, and then the locale exactly. * * @since 1.5.0 * @since 4.6.0 The function now tries to load the .mo file from the languages directory first. * * @param string $domain Unique identifier for retrieving translated strings * @param string $deprecated Optional. Use the $plugin_rel_path parameter instead. Default false. * @param string $plugin_rel_path Optional. Relative path to WP_PLUGIN_DIR where the .mo file resides. * Default false. * @return bool True when textdomain is successfully loaded, false otherwise. */ function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path = false ) { /** * Filters a plugin's locale. * * @since 3.0.0 * * @param string $locale The plugin's current locale. * @param string $domain Text domain. Unique identifier for retrieving translated strings. */ $locale = apply_filters( 'plugin_locale', determine_locale(), $domain ); $mofile = $domain . '-' . $locale . '.mo'; // Try to load from the languages directory first. if ( load_textdomain( $domain, WP_LANG_DIR . '/plugins/' . $mofile ) ) { return true; } if ( false !== $plugin_rel_path ) { $path = WP_PLUGIN_DIR . '/' . trim( $plugin_rel_path, '/' ); } elseif ( false !== $deprecated ) { _deprecated_argument( __FUNCTION__, '2.7.0' ); $path = ABSPATH . trim( $deprecated, '/' ); } else { $path = WP_PLUGIN_DIR; } return load_textdomain( $domain, $path . '/' . $mofile ); } /** * Load the translated strings for a plugin residing in the mu-plugins directory. * * @since 3.0.0 * @since 4.6.0 The function now tries to load the .mo file from the languages directory first. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @param string $mu_plugin_rel_path Optional. Relative to `WPMU_PLUGIN_DIR` directory in which the .mo * file resides. Default empty string. * @return bool True when textdomain is successfully loaded, false otherwise. */ function load_muplugin_textdomain( $domain, $mu_plugin_rel_path = '' ) { /** This filter is documented in wp-includes/l10n.php */ $locale = apply_filters( 'plugin_locale', determine_locale(), $domain ); $mofile = $domain . '-' . $locale . '.mo'; // Try to load from the languages directory first. if ( load_textdomain( $domain, WP_LANG_DIR . '/plugins/' . $mofile ) ) { return true; } $path = WPMU_PLUGIN_DIR . '/' . ltrim( $mu_plugin_rel_path, '/' ); return load_textdomain( $domain, $path . '/' . $mofile ); } /** * Load the theme's translated strings. * * If the current locale exists as a .mo file in the theme's root directory, it * will be included in the translated strings by the $domain. * * The .mo files must be named based on the locale exactly. * * @since 1.5.0 * @since 4.6.0 The function now tries to load the .mo file from the languages directory first. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @param string $path Optional. Path to the directory containing the .mo file. * Default false. * @return bool True when textdomain is successfully loaded, false otherwise. */ function load_theme_textdomain( $domain, $path = false ) { /** * Filters a theme's locale. * * @since 3.0.0 * * @param string $locale The theme's current locale. * @param string $domain Text domain. Unique identifier for retrieving translated strings. */ $locale = apply_filters( 'theme_locale', determine_locale(), $domain ); $mofile = $domain . '-' . $locale . '.mo'; // Try to load from the languages directory first. if ( load_textdomain( $domain, WP_LANG_DIR . '/themes/' . $mofile ) ) { return true; } if ( ! $path ) { $path = get_template_directory(); } return load_textdomain( $domain, $path . '/' . $locale . '.mo' ); } /** * Load the child themes translated strings. * * If the current locale exists as a .mo file in the child themes * root directory, it will be included in the translated strings by the $domain. * * The .mo files must be named based on the locale exactly. * * @since 2.9.0 * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @param string $path Optional. Path to the directory containing the .mo file. * Default false. * @return bool True when the theme textdomain is successfully loaded, false otherwise. */ function load_child_theme_textdomain( $domain, $path = false ) { if ( ! $path ) { $path = get_stylesheet_directory(); } return load_theme_textdomain( $domain, $path ); } /** * Loads the script translated strings. * * @since 5.0.0 * @since 5.0.2 Uses load_script_translations() to load translation data. * @since 5.1.0 The `$domain` parameter was made optional. * * @see WP_Scripts::set_translations() * * @param string $handle Name of the script to register a translation domain to. * @param string $domain Optional. Text domain. Default 'default'. * @param string $path Optional. The full file path to the directory containing translation files. * * @return false|string False if the script textdomain could not be loaded, the translated strings * in JSON encoding otherwise. */ function load_script_textdomain( $handle, $domain = 'default', $path = null ) { $wp_scripts = wp_scripts(); if ( ! isset( $wp_scripts->registered[ $handle ] ) ) { return false; } $path = untrailingslashit( $path ); $locale = determine_locale(); // If a path was given and the handle file exists simply return it. $file_base = $domain === 'default' ? $locale : $domain . '-' . $locale; $handle_filename = $file_base . '-' . $handle . '.json'; if ( $path ) { $translations = load_script_translations( $path . '/' . $handle_filename, $handle, $domain ); if ( $translations ) { return $translations; } } $src = $wp_scripts->registered[ $handle ]->src; if ( ! preg_match( '|^(https?:)?//|', $src ) && ! ( $wp_scripts->content_url && 0 === strpos( $src, $wp_scripts->content_url ) ) ) { $src = $wp_scripts->base_url . $src; } $relative = false; $languages_path = WP_LANG_DIR; $src_url = wp_parse_url( $src ); $content_url = wp_parse_url( content_url() ); $site_url = wp_parse_url( site_url() ); // If the host is the same or it's a relative URL. if ( strpos( $src_url['path'], $content_url['path'] ) === 0 && ( ! isset( $src_url['host'] ) || $src_url['host'] === $content_url['host'] ) ) { // Make the src relative the specific plugin or theme. $relative = trim( substr( $src_url['path'], strlen( $content_url['path'] ) ), '/' ); $relative = explode( '/', $relative ); $languages_path = WP_LANG_DIR . '/' . $relative[0]; $relative = array_slice( $relative, 2 ); $relative = implode( '/', $relative ); } elseif ( ! isset( $src_url['host'] ) || $src_url['host'] === $site_url['host'] ) { if ( ! isset( $site_url['path'] ) ) { $relative = trim( $src_url['path'], '/' ); } elseif ( ( strpos( $src_url['path'], trailingslashit( $site_url['path'] ) ) === 0 ) ) { // Make the src relative to the WP root. $relative = substr( $src_url['path'], strlen( $site_url['path'] ) ); $relative = trim( $relative, '/' ); } } /** * Filters the relative path of scripts used for finding translation files. * * @since 5.0.2 * * @param string $relative The relative path of the script. False if it could not be determined. * @param string $src The full source url of the script. */ $relative = apply_filters( 'load_script_textdomain_relative_path', $relative, $src ); // If the source is not from WP. if ( false === $relative ) { return load_script_translations( false, $handle, $domain ); } // Translations are always based on the unminified filename. if ( substr( $relative, -7 ) === '.min.js' ) { $relative = substr( $relative, 0, -7 ) . '.js'; } $md5_filename = $file_base . '-' . md5( $relative ) . '.json'; if ( $path ) { $translations = load_script_translations( $path . '/' . $md5_filename, $handle, $domain ); if ( $translations ) { return $translations; } } $translations = load_script_translations( $languages_path . '/' . $md5_filename, $handle, $domain ); if ( $translations ) { return $translations; } return load_script_translations( false, $handle, $domain ); } /** * Loads the translation data for the given script handle and text domain. * * @since 5.0.2 * * @param string|false $file Path to the translation file to load. False if there isn't one. * @param string $handle Name of the script to register a translation domain to. * @param string $domain The text domain. * @return string|false The JSON-encoded translated strings for the given script handle and text domain. False if there are none. */ function load_script_translations( $file, $handle, $domain ) { /** * Pre-filters script translations for the given file, script handle and text domain. * * Returning a non-null value allows to override the default logic, effectively short-circuiting the function. * * @since 5.0.2 * * @param string|false $translations JSON-encoded translation data. Default null. * @param string|false $file Path to the translation file to load. False if there isn't one. * @param string $handle Name of the script to register a translation domain to. * @param string $domain The text domain. */ $translations = apply_filters( 'pre_load_script_translations', null, $file, $handle, $domain ); if ( null !== $translations ) { return $translations; } /** * Filters the file path for loading script translations for the given script handle and text domain. * * @since 5.0.2 * * @param string|false $file Path to the translation file to load. False if there isn't one. * @param string $handle Name of the script to register a translation domain to. * @param string $domain The text domain. */ $file = apply_filters( 'load_script_translation_file', $file, $handle, $domain ); if ( ! $file || ! is_readable( $file ) ) { return false; } $translations = file_get_contents( $file ); /** * Filters script translations for the given file, script handle and text domain. * * @since 5.0.2 * * @param string $translations JSON-encoded translation data. * @param string $file Path to the translation file that was loaded. * @param string $handle Name of the script to register a translation domain to. * @param string $domain The text domain. */ return apply_filters( 'load_script_translations', $translations, $file, $handle, $domain ); } /** * Loads plugin and theme textdomains just-in-time. * * When a textdomain is encountered for the first time, we try to load * the translation file from `wp-content/languages`, removing the need * to call load_plugin_texdomain() or load_theme_texdomain(). * * @since 4.6.0 * @access private * * @see get_translations_for_domain() * @global MO[] $l10n_unloaded An array of all text domains that have been unloaded again. * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @return bool True when the textdomain is successfully loaded, false otherwise. */ function _load_textdomain_just_in_time( $domain ) { global $l10n_unloaded; $l10n_unloaded = (array) $l10n_unloaded; // Short-circuit if domain is 'default' which is reserved for core. if ( 'default' === $domain || isset( $l10n_unloaded[ $domain ] ) ) { return false; } $translation_path = _get_path_to_translation( $domain ); if ( false === $translation_path ) { return false; } return load_textdomain( $domain, $translation_path ); } /** * Gets the path to a translation file for loading a textdomain just in time. * * Caches the retrieved results internally. * * @since 4.7.0 * @access private * * @see _load_textdomain_just_in_time() * @staticvar array $available_translations * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @param bool $reset Whether to reset the internal cache. Used by the switch to locale functionality. * @return string|false The path to the translation file or false if no translation file was found. */ function _get_path_to_translation( $domain, $reset = false ) { static $available_translations = array(); if ( true === $reset ) { $available_translations = array(); } if ( ! isset( $available_translations[ $domain ] ) ) { $available_translations[ $domain ] = _get_path_to_translation_from_lang_dir( $domain ); } return $available_translations[ $domain ]; } /** * Gets the path to a translation file in the languages directory for the current locale. * * Holds a cached list of available .mo files to improve performance. * * @since 4.7.0 * @access private * * @see _get_path_to_translation() * @staticvar array $cached_mofiles * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @return string|false The path to the translation file or false if no translation file was found. */ function _get_path_to_translation_from_lang_dir( $domain ) { static $cached_mofiles = null; if ( null === $cached_mofiles ) { $cached_mofiles = array(); $locations = array( WP_LANG_DIR . '/plugins', WP_LANG_DIR . '/themes', ); foreach ( $locations as $location ) { $mofiles = glob( $location . '/*.mo' ); if ( $mofiles ) { $cached_mofiles = array_merge( $cached_mofiles, $mofiles ); } } } $locale = determine_locale(); $mofile = "{$domain}-{$locale}.mo"; $path = WP_LANG_DIR . '/plugins/' . $mofile; if ( in_array( $path, $cached_mofiles ) ) { return $path; } $path = WP_LANG_DIR . '/themes/' . $mofile; if ( in_array( $path, $cached_mofiles ) ) { return $path; } return false; } /** * Return the Translations instance for a text domain. * * If there isn't one, returns empty Translations instance. * * @since 2.8.0 * * @global MO[] $l10n * @staticvar NOOP_Translations $noop_translations * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @return Translations|NOOP_Translations A Translations instance. */ function get_translations_for_domain( $domain ) { global $l10n; if ( isset( $l10n[ $domain ] ) || ( _load_textdomain_just_in_time( $domain ) && isset( $l10n[ $domain ] ) ) ) { return $l10n[ $domain ]; } static $noop_translations = null; if ( null === $noop_translations ) { $noop_translations = new NOOP_Translations; } return $noop_translations; } /** * Whether there are translations for the text domain. * * @since 3.0.0 * * @global MO[] $l10n * * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @return bool Whether there are translations. */ function is_textdomain_loaded( $domain ) { global $l10n; return isset( $l10n[ $domain ] ); } /** * Translates role name. * * Since the role names are in the database and not in the source there * are dummy gettext calls to get them into the POT file and this function * properly translates them back. * * The before_last_bar() call is needed, because older installations keep the roles * using the old context format: 'Role name|User role' and just skipping the * content after the last bar is easier than fixing them in the DB. New installations * won't suffer from that problem. * * @since 2.8.0 * * @param string $name The role name. * @return string Translated role name on success, original name on failure. */ function translate_user_role( $name ) { return translate_with_gettext_context( before_last_bar( $name ), 'User role' ); } /** * Get all available languages based on the presence of *.mo files in a given directory. * * The default directory is WP_LANG_DIR. * * @since 3.0.0 * @since 4.7.0 The results are now filterable with the {@see 'get_available_languages'} filter. * * @param string $dir A directory to search for language files. * Default WP_LANG_DIR. * @return array An array of language codes or an empty array if no languages are present. Language codes are formed by stripping the .mo extension from the language file names. */ function get_available_languages( $dir = null ) { $languages = array(); $lang_files = glob( ( is_null( $dir ) ? WP_LANG_DIR : $dir ) . '/*.mo' ); if ( $lang_files ) { foreach ( $lang_files as $lang_file ) { $lang_file = basename( $lang_file, '.mo' ); if ( 0 !== strpos( $lang_file, 'continents-cities' ) && 0 !== strpos( $lang_file, 'ms-' ) && 0 !== strpos( $lang_file, 'admin-' ) ) { $languages[] = $lang_file; } } } /** * Filters the list of available language codes. * * @since 4.7.0 * * @param array $languages An array of available language codes. * @param string $dir The directory where the language files were found. */ return apply_filters( 'get_available_languages', $languages, $dir ); } /** * Get installed translations. * * Looks in the wp-content/languages directory for translations of * plugins or themes. * * @since 3.7.0 * * @param string $type What to search for. Accepts 'plugins', 'themes', 'core'. * @return array Array of language data. */ function wp_get_installed_translations( $type ) { if ( $type !== 'themes' && $type !== 'plugins' && $type !== 'core' ) { return array(); } $dir = 'core' === $type ? '' : "/$type"; if ( ! is_dir( WP_LANG_DIR ) ) { return array(); } if ( $dir && ! is_dir( WP_LANG_DIR . $dir ) ) { return array(); } $files = scandir( WP_LANG_DIR . $dir ); if ( ! $files ) { return array(); } $language_data = array(); foreach ( $files as $file ) { if ( '.' === $file[0] || is_dir( WP_LANG_DIR . "$dir/$file" ) ) { continue; } if ( substr( $file, -3 ) !== '.po' ) { continue; } if ( ! preg_match( '/(?:(.+)-)?([a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?).po/', $file, $match ) ) { continue; } if ( ! in_array( substr( $file, 0, -3 ) . '.mo', $files ) ) { continue; } list( , $textdomain, $language ) = $match; if ( '' === $textdomain ) { $textdomain = 'default'; } $language_data[ $textdomain ][ $language ] = wp_get_pomo_file_data( WP_LANG_DIR . "$dir/$file" ); } return $language_data; } /** * Extract headers from a PO file. * * @since 3.7.0 * * @param string $po_file Path to PO file. * @return array PO file headers. */ function wp_get_pomo_file_data( $po_file ) { $headers = get_file_data( $po_file, array( 'POT-Creation-Date' => '"POT-Creation-Date', 'PO-Revision-Date' => '"PO-Revision-Date', 'Project-Id-Version' => '"Project-Id-Version', 'X-Generator' => '"X-Generator', ) ); foreach ( $headers as $header => $value ) { // Remove possible contextual '\n' and closing double quote. $headers[ $header ] = preg_replace( '~(\\\n)?"$~', '', $value ); } return $headers; } /** * Language selector. * * @since 4.0.0 * @since 4.3.0 Introduced the `echo` argument. * @since 4.7.0 Introduced the `show_option_site_default` argument. * @since 5.1.0 Introduced the `show_option_en_us` argument. * * @see get_available_languages() * @see wp_get_available_translations() * * @param string|array $args { * Optional. Array or string of arguments for outputting the language selector. * * @type string $id ID attribute of the select element. Default 'locale'. * @type string $name Name attribute of the select element. Default 'locale'. * @type array $languages List of installed languages, contain only the locales. * Default empty array. * @type array $translations List of available translations. Default result of * wp_get_available_translations(). * @type string $selected Language which should be selected. Default empty. * @type bool|int $echo Whether to echo the generated markup. Accepts 0, 1, or their * boolean equivalents. Default 1. * @type bool $show_available_translations Whether to show available translations. Default true. * @type bool $show_option_site_default Whether to show an option to fall back to the site's locale. Default false. * @type bool $show_option_en_us Whether to show an option for English (United States). Default true. * } * @return string HTML content */ function wp_dropdown_languages( $args = array() ) { $parsed_args = wp_parse_args( $args, array( 'id' => 'locale', 'name' => 'locale', 'languages' => array(), 'translations' => array(), 'selected' => '', 'echo' => 1, 'show_available_translations' => true, 'show_option_site_default' => false, 'show_option_en_us' => true, ) ); // Bail if no ID or no name. if ( ! $parsed_args['id'] || ! $parsed_args['name'] ) { return; } // English (United States) uses an empty string for the value attribute. if ( 'en_US' === $parsed_args['selected'] ) { $parsed_args['selected'] = ''; } $translations = $parsed_args['translations']; if ( empty( $translations ) ) { require_once( ABSPATH . 'wp-admin/includes/translation-install.php' ); $translations = wp_get_available_translations(); } /* * $parsed_args['languages'] should only contain the locales. Find the locale in * $translations to get the native name. Fall back to locale. */ $languages = array(); foreach ( $parsed_args['languages'] as $locale ) { if ( isset( $translations[ $locale ] ) ) { $translation = $translations[ $locale ]; $languages[] = array( 'language' => $translation['language'], 'native_name' => $translation['native_name'], 'lang' => current( $translation['iso'] ), ); // Remove installed language from available translations. unset( $translations[ $locale ] ); } else { $languages[] = array( 'language' => $locale, 'native_name' => $locale, 'lang' => '', ); } } $translations_available = ( ! empty( $translations ) && $parsed_args['show_available_translations'] ); // Holds the HTML markup. $structure = array(); // List installed languages. if ( $translations_available ) { $structure[] = ''; } // Site default. if ( $parsed_args['show_option_site_default'] ) { $structure[] = sprintf( '', selected( 'site-default', $parsed_args['selected'], false ), _x( 'Site Default', 'default site language' ) ); } if ( $parsed_args['show_option_en_us'] ) { $structure[] = sprintf( '', selected( '', $parsed_args['selected'], false ) ); } // List installed languages. foreach ( $languages as $language ) { $structure[] = sprintf( '', esc_attr( $language['language'] ), esc_attr( $language['lang'] ), selected( $language['language'], $parsed_args['selected'], false ), esc_html( $language['native_name'] ) ); } if ( $translations_available ) { $structure[] = ''; } // List available translations. if ( $translations_available ) { $structure[] = ''; foreach ( $translations as $translation ) { $structure[] = sprintf( '', esc_attr( $translation['language'] ), esc_attr( current( $translation['iso'] ) ), selected( $translation['language'], $parsed_args['selected'], false ), esc_html( $translation['native_name'] ) ); } $structure[] = ''; } // Combine the output string. $output = sprintf( ''; if ( $parsed_args['echo'] ) { echo $output; } return $output; } /** * Determines whether the current locale is right-to-left (RTL). * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 3.0.0 * * @global WP_Locale $wp_locale * * @return bool Whether locale is RTL. */ function is_rtl() { global $wp_locale; if ( ! ( $wp_locale instanceof WP_Locale ) ) { return false; } return $wp_locale->is_rtl(); } /** * Switches the translations according to the given locale. * * @since 4.7.0 * * @global WP_Locale_Switcher $wp_locale_switcher * * @param string $locale The locale. * @return bool True on success, false on failure. */ function switch_to_locale( $locale ) { /* @var WP_Locale_Switcher $wp_locale_switcher */ global $wp_locale_switcher; return $wp_locale_switcher->switch_to_locale( $locale ); } /** * Restores the translations according to the previous locale. * * @since 4.7.0 * * @global WP_Locale_Switcher $wp_locale_switcher * * @return string|false Locale on success, false on error. */ function restore_previous_locale() { /* @var WP_Locale_Switcher $wp_locale_switcher */ global $wp_locale_switcher; return $wp_locale_switcher->restore_previous_locale(); } /** * Restores the translations according to the original locale. * * @since 4.7.0 * * @global WP_Locale_Switcher $wp_locale_switcher * * @return string|false Locale on success, false on error. */ function restore_current_locale() { /* @var WP_Locale_Switcher $wp_locale_switcher */ global $wp_locale_switcher; return $wp_locale_switcher->restore_current_locale(); } /** * Whether switch_to_locale() is in effect. * * @since 4.7.0 * * @global WP_Locale_Switcher $wp_locale_switcher * * @return bool True if the locale has been switched, false otherwise. */ function is_locale_switched() { /* @var WP_Locale_Switcher $wp_locale_switcher */ global $wp_locale_switcher; return $wp_locale_switcher->is_switched(); } /** * Locale API: WP_Locale_Switcher class * * @package WordPress * @subpackage i18n * @since 4.7.0 */ /** * Core class used for switching locales. * * @since 4.7.0 */ class WP_Locale_Switcher { /** * Locale stack. * * @since 4.7.0 * @var string[] */ private $locales = array(); /** * Original locale. * * @since 4.7.0 * @var string */ private $original_locale; /** * Holds all available languages. * * @since 4.7.0 * @var array An array of language codes (file names without the .mo extension). */ private $available_languages = array(); /** * Constructor. * * Stores the original locale as well as a list of all available languages. * * @since 4.7.0 */ public function __construct() { $this->original_locale = determine_locale(); $this->available_languages = array_merge( array( 'en_US' ), get_available_languages() ); } /** * Initializes the locale switcher. * * Hooks into the {@see 'locale'} filter to change the locale on the fly. * * @since 4.7.0 */ public function init() { add_filter( 'locale', array( $this, 'filter_locale' ) ); } /** * Switches the translations according to the given locale. * * @since 4.7.0 * * @param string $locale The locale to switch to. * @return bool True on success, false on failure. */ public function switch_to_locale( $locale ) { $current_locale = determine_locale(); if ( $current_locale === $locale ) { return false; } if ( ! in_array( $locale, $this->available_languages, true ) ) { return false; } $this->locales[] = $locale; $this->change_locale( $locale ); /** * Fires when the locale is switched. * * @since 4.7.0 * * @param string $locale The new locale. */ do_action( 'switch_locale', $locale ); return true; } /** * Restores the translations according to the previous locale. * * @since 4.7.0 * * @return string|false Locale on success, false on failure. */ public function restore_previous_locale() { $previous_locale = array_pop( $this->locales ); if ( null === $previous_locale ) { // The stack is empty, bail. return false; } $locale = end( $this->locales ); if ( ! $locale ) { // There's nothing left in the stack: go back to the original locale. $locale = $this->original_locale; } $this->change_locale( $locale ); /** * Fires when the locale is restored to the previous one. * * @since 4.7.0 * * @param string $locale The new locale. * @param string $previous_locale The previous locale. */ do_action( 'restore_previous_locale', $locale, $previous_locale ); return $locale; } /** * Restores the translations according to the original locale. * * @since 4.7.0 * * @return string|false Locale on success, false on failure. */ public function restore_current_locale() { if ( empty( $this->locales ) ) { return false; } $this->locales = array( $this->original_locale ); return $this->restore_previous_locale(); } /** * Whether switch_to_locale() is in effect. * * @since 4.7.0 * * @return bool True if the locale has been switched, false otherwise. */ public function is_switched() { return ! empty( $this->locales ); } /** * Filters the locale of the WordPress installation. * * @since 4.7.0 * * @param string $locale The locale of the WordPress installation. * @return string The locale currently being switched to. */ public function filter_locale( $locale ) { $switched_locale = end( $this->locales ); if ( $switched_locale ) { return $switched_locale; } return $locale; } /** * Load translations for a given locale. * * When switching to a locale, translations for this locale must be loaded from scratch. * * @since 4.7.0 * * @global Mo[] $l10n An array of all currently loaded text domains. * * @param string $locale The locale to load translations for. */ private function load_translations( $locale ) { global $l10n; $domains = $l10n ? array_keys( $l10n ) : array(); load_default_textdomain( $locale ); foreach ( $domains as $domain ) { if ( 'default' === $domain ) { continue; } unload_textdomain( $domain ); get_translations_for_domain( $domain ); } } /** * Changes the site's locale to the given one. * * Loads the translations, changes the global `$wp_locale` object and updates * all post type labels. * * @since 4.7.0 * * @global WP_Locale $wp_locale The WordPress date and time locale object. * * @param string $locale The locale to change to. */ private function change_locale( $locale ) { // Reset translation availability information. _get_path_to_translation( null, true ); $this->load_translations( $locale ); $GLOBALS['wp_locale'] = new WP_Locale(); /** * Fires when the locale is switched to or restored. * * @since 4.7.0 * * @param string $locale The new locale. */ do_action( 'change_locale', $locale ); } }