/** * 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; // SPL can be disabled on PHP 5.2 if ( ! function_exists( 'spl_autoload_register' ) ): $_wp_spl_autoloaders = array(); /** * Autoloader compatibility callback. * * @since 4.6.0 * * @param string $classname Class to attempt autoloading. */ function __autoload( $classname ) { global $_wp_spl_autoloaders; foreach ( $_wp_spl_autoloaders as $autoloader ) { if ( ! is_callable( $autoloader ) ) { // Avoid the extra warning if the autoloader isn't callable. continue; } call_user_func( $autoloader, $classname ); // If it has been autoloaded, stop processing. if ( class_exists( $classname, false ) ) { return; } } } /** * Registers a function to be autoloaded. * * @since 4.6.0 * * @param callable $autoload_function The function to register. * @param bool $throw Optional. Whether the function should throw an exception * if the function isn't callable. Default true. * @param bool $prepend Whether the function should be prepended to the stack. * Default false. */ function spl_autoload_register( $autoload_function, $throw = true, $prepend = false ) { if ( $throw && ! is_callable( $autoload_function ) ) { // String not translated to match PHP core. throw new Exception( 'Function not callable' ); } global $_wp_spl_autoloaders; // Don't allow multiple registration. if ( in_array( $autoload_function, $_wp_spl_autoloaders ) ) { return; } if ( $prepend ) { array_unshift( $_wp_spl_autoloaders, $autoload_function ); } else { $_wp_spl_autoloaders[] = $autoload_function; } } /** * Unregisters an autoloader function. * * @since 4.6.0 * * @param callable $function The function to unregister. * @return bool True if the function was unregistered, false if it could not be. */ function spl_autoload_unregister( $function ) { global $_wp_spl_autoloaders; foreach ( $_wp_spl_autoloaders as &$autoloader ) { if ( $autoloader === $function ) { unset( $autoloader ); return true; } } return false; } /** * Retrieves the registered autoloader functions. * * @since 4.6.0 * * @return array List of autoloader functions. */ function spl_autoload_functions() { return $GLOBALS['_wp_spl_autoloaders']; } endif; /** * Session API: WP_User_Meta_Session_Tokens class * * @package WordPress * @subpackage Session * @since 4.7.0 */ /** * Meta-based user sessions token manager. * * @since 4.0.0 */ class WP_User_Meta_Session_Tokens extends WP_Session_Tokens { /** * Get all sessions of a user. * * @since 4.0.0 * @access protected * * @return array Sessions of a user. */ protected function get_sessions() { $sessions = get_user_meta( $this->user_id, 'session_tokens', true ); if ( ! is_array( $sessions ) ) { return array(); } $sessions = array_map( array( $this, 'prepare_session' ), $sessions ); return array_filter( $sessions, array( $this, 'is_still_valid' ) ); } /** * Converts an expiration to an array of session information. * * @param mixed $session Session or expiration. * @return array Session. */ protected function prepare_session( $session ) { if ( is_int( $session ) ) { return array( 'expiration' => $session ); } return $session; } /** * Retrieve a session by its verifier (token hash). * * @since 4.0.0 * @access protected * * @param string $verifier Verifier of the session to retrieve. * @return array|null The session, or null if it does not exist */ protected function get_session( $verifier ) { $sessions = $this->get_sessions(); if ( isset( $sessions[ $verifier ] ) ) { return $sessions[ $verifier ]; } return null; } /** * Update a session by its verifier. * * @since 4.0.0 * @access protected * * @param string $verifier Verifier of the session to update. * @param array $session Optional. Session. Omitting this argument destroys the session. */ protected function update_session( $verifier, $session = null ) { $sessions = $this->get_sessions(); if ( $session ) { $sessions[ $verifier ] = $session; } else { unset( $sessions[ $verifier ] ); } $this->update_sessions( $sessions ); } /** * Update a user's sessions in the usermeta table. * * @since 4.0.0 * @access protected * * @param array $sessions Sessions. */ protected function update_sessions( $sessions ) { if ( $sessions ) { update_user_meta( $this->user_id, 'session_tokens', $sessions ); } else { delete_user_meta( $this->user_id, 'session_tokens' ); } } /** * Destroy all session tokens for a user, except a single session passed. * * @since 4.0.0 * @access protected * * @param string $verifier Verifier of the session to keep. */ protected function destroy_other_sessions( $verifier ) { $session = $this->get_session( $verifier ); $this->update_sessions( array( $verifier => $session ) ); } /** * Destroy all session tokens for a user. * * @since 4.0.0 * @access protected */ protected function destroy_all_sessions() { $this->update_sessions( array() ); } /** * Destroy all session tokens for all users. * * @since 4.0.0 * @access public * @static */ public static function drop_sessions() { delete_metadata( 'user', 0, 'session_tokens', false, true ); } } /** * Meta API: WP_Meta_Query class * * @package WordPress * @subpackage Meta * @since 4.4.0 */ /** * Core class used to implement meta queries for the Meta API. * * Used for generating SQL clauses that filter a primary query according to metadata keys and values. * * WP_Meta_Query is a helper that allows primary query classes, such as WP_Query and WP_User_Query, * * to filter their results by object metadata, by generating `JOIN` and `WHERE` subclauses to be attached * to the primary SQL query string. * * @since 3.2.0 * @package WordPress * @subpackage Meta */ class WP_Meta_Query { /** * Array of metadata queries. * * See WP_Meta_Query::__construct() for information on meta query arguments. * * @since 3.2.0 * @access public * @var array */ public $queries = array(); /** * The relation between the queries. Can be one of 'AND' or 'OR'. * * @since 3.2.0 * @access public * @var string */ public $relation; /** * Database table to query for the metadata. * * @since 4.1.0 * @access public * @var string */ public $meta_table; /** * Column in meta_table that represents the ID of the object the metadata belongs to. * * @since 4.1.0 * @access public * @var string */ public $meta_id_column; /** * Database table that where the metadata's objects are stored (eg $wpdb->users). * * @since 4.1.0 * @access public * @var string */ public $primary_table; /** * Column in primary_table that represents the ID of the object. * * @since 4.1.0 * @access public * @var string */ public $primary_id_column; /** * A flat list of table aliases used in JOIN clauses. * * @since 4.1.0 * @access protected * @var array */ protected $table_aliases = array(); /** * A flat list of clauses, keyed by clause 'name'. * * @since 4.2.0 * @access protected * @var array */ protected $clauses = array(); /** * Whether the query contains any OR relations. * * @since 4.3.0 * @access protected * @var bool */ protected $has_or_relation = false; /** * Constructor. * * @since 3.2.0 * @since 4.2.0 Introduced support for naming query clauses by associative array keys. * * @access public * * @param array $meta_query { * Array of meta query clauses. When first-order clauses or sub-clauses use strings as * their array keys, they may be referenced in the 'orderby' parameter of the parent query. * * @type string $relation Optional. The MySQL keyword used to join * the clauses of the query. Accepts 'AND', or 'OR'. Default 'AND'. * @type array { * Optional. An array of first-order clause parameters, or another fully-formed meta query. * * @type string $key Meta key to filter by. * @type string $value Meta value to filter by. * @type string $compare MySQL operator used for comparing the $value. Accepts '=', * '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', * 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'REGEXP', * 'NOT REGEXP', 'RLIKE', 'EXISTS' or 'NOT EXISTS'. * Default is 'IN' when `$value` is an array, '=' otherwise. * @type string $type MySQL data type that the meta_value column will be CAST to for * comparisons. Accepts 'NUMERIC', 'BINARY', 'CHAR', 'DATE', * 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', or 'UNSIGNED'. * Default is 'CHAR'. * } * } */ public function __construct( $meta_query = false ) { if ( !$meta_query ) return; if ( isset( $meta_query['relation'] ) && strtoupper( $meta_query['relation'] ) == 'OR' ) { $this->relation = 'OR'; } else { $this->relation = 'AND'; } $this->queries = $this->sanitize_query( $meta_query ); } /** * Ensure the 'meta_query' argument passed to the class constructor is well-formed. * * Eliminates empty items and ensures that a 'relation' is set. * * @since 4.1.0 * @access public * * @param array $queries Array of query clauses. * @return array Sanitized array of query clauses. */ public function sanitize_query( $queries ) { $clean_queries = array(); if ( ! is_array( $queries ) ) { return $clean_queries; } foreach ( $queries as $key => $query ) { if ( 'relation' === $key ) { $relation = $query; } elseif ( ! is_array( $query ) ) { continue; // First-order clause. } elseif ( $this->is_first_order_clause( $query ) ) { if ( isset( $query['value'] ) && array() === $query['value'] ) { unset( $query['value'] ); } $clean_queries[ $key ] = $query; // Otherwise, it's a nested query, so we recurse. } else { $cleaned_query = $this->sanitize_query( $query ); if ( ! empty( $cleaned_query ) ) { $clean_queries[ $key ] = $cleaned_query; } } } if ( empty( $clean_queries ) ) { return $clean_queries; } // Sanitize the 'relation' key provided in the query. if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) { $clean_queries['relation'] = 'OR'; $this->has_or_relation = true; /* * If there is only a single clause, call the relation 'OR'. * This value will not actually be used to join clauses, but it * simplifies the logic around combining key-only queries. */ } elseif ( 1 === count( $clean_queries ) ) { $clean_queries['relation'] = 'OR'; // Default to AND. } else { $clean_queries['relation'] = 'AND'; } return $clean_queries; } /** * Determine whether a query clause is first-order. * * A first-order meta query clause is one that has either a 'key' or * a 'value' array key. * * @since 4.1.0 * @access protected * * @param array $query Meta query arguments. * @return bool Whether the query clause is a first-order clause. */ protected function is_first_order_clause( $query ) { return isset( $query['key'] ) || isset( $query['value'] ); } /** * Constructs a meta query based on 'meta_*' query vars * * @since 3.2.0 * @access public * * @param array $qv The query variables */ public function parse_query_vars( $qv ) { $meta_query = array(); /* * For orderby=meta_value to work correctly, simple query needs to be * first (so that its table join is against an unaliased meta table) and * needs to be its own clause (so it doesn't interfere with the logic of * the rest of the meta_query). */ $primary_meta_query = array(); foreach ( array( 'key', 'compare', 'type' ) as $key ) { if ( ! empty( $qv[ "meta_$key" ] ) ) { $primary_meta_query[ $key ] = $qv[ "meta_$key" ]; } } // WP_Query sets 'meta_value' = '' by default. if ( isset( $qv['meta_value'] ) && '' !== $qv['meta_value'] && ( ! is_array( $qv['meta_value'] ) || $qv['meta_value'] ) ) { $primary_meta_query['value'] = $qv['meta_value']; } $existing_meta_query = isset( $qv['meta_query'] ) && is_array( $qv['meta_query'] ) ? $qv['meta_query'] : array(); if ( ! empty( $primary_meta_query ) && ! empty( $existing_meta_query ) ) { $meta_query = array( 'relation' => 'AND', $primary_meta_query, $existing_meta_query, ); } elseif ( ! empty( $primary_meta_query ) ) { $meta_query = array( $primary_meta_query, ); } elseif ( ! empty( $existing_meta_query ) ) { $meta_query = $existing_meta_query; } $this->__construct( $meta_query ); } /** * Return the appropriate alias for the given meta type if applicable. * * @since 3.7.0 * @access public * * @param string $type MySQL type to cast meta_value. * @return string MySQL type. */ public function get_cast_for_type( $type = '' ) { if ( empty( $type ) ) return 'CHAR'; $meta_type = strtoupper( $type ); if ( ! preg_match( '/^(?:BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|NUMERIC(?:\(\d+(?:,\s?\d+)?\))?|DECIMAL(?:\(\d+(?:,\s?\d+)?\))?)$/', $meta_type ) ) return 'CHAR'; if ( 'NUMERIC' == $meta_type ) $meta_type = 'SIGNED'; return $meta_type; } /** * Generates SQL clauses to be appended to a main query. * * @since 3.2.0 * @access public * * @param string $type Type of meta, eg 'user', 'post'. * @param string $primary_table Database table where the object being filtered is stored (eg wp_users). * @param string $primary_id_column ID column for the filtered object in $primary_table. * @param object $context Optional. The main query object. * @return false|array { * Array containing JOIN and WHERE SQL clauses to append to the main query. * * @type string $join SQL fragment to append to the main JOIN clause. * @type string $where SQL fragment to append to the main WHERE clause. * } */ public function get_sql( $type, $primary_table, $primary_id_column, $context = null ) { if ( ! $meta_table = _get_meta_table( $type ) ) { return false; } $this->table_aliases = array(); $this->meta_table = $meta_table; $this->meta_id_column = sanitize_key( $type . '_id' ); $this->primary_table = $primary_table; $this->primary_id_column = $primary_id_column; $sql = $this->get_sql_clauses(); /* * If any JOINs are LEFT JOINs (as in the case of NOT EXISTS), then all JOINs should * be LEFT. Otherwise posts with no metadata will be excluded from results. */ if ( false !== strpos( $sql['join'], 'LEFT JOIN' ) ) { $sql['join'] = str_replace( 'INNER JOIN', 'LEFT JOIN', $sql['join'] ); } /** * Filters the meta query's generated SQL. * * @since 3.1.0 * * @param array $clauses Array containing the query's JOIN and WHERE clauses. * @param array $queries Array of meta queries. * @param string $type Type of meta. * @param string $primary_table Primary table. * @param string $primary_id_column Primary column ID. * @param object $context The main query object. */ return apply_filters_ref_array( 'get_meta_sql', array( $sql, $this->queries, $type, $primary_table, $primary_id_column, $context ) ); } /** * Generate SQL clauses to be appended to a main query. * * Called by the public WP_Meta_Query::get_sql(), this method is abstracted * out to maintain parity with the other Query classes. * * @since 4.1.0 * @access protected * * @return array { * Array containing JOIN and WHERE SQL clauses to append to the main query. * * @type string $join SQL fragment to append to the main JOIN clause. * @type string $where SQL fragment to append to the main WHERE clause. * } */ protected function get_sql_clauses() { /* * $queries are passed by reference to get_sql_for_query() for recursion. * To keep $this->queries unaltered, pass a copy. */ $queries = $this->queries; $sql = $this->get_sql_for_query( $queries ); if ( ! empty( $sql['where'] ) ) { $sql['where'] = ' AND ' . $sql['where']; } return $sql; } /** * Generate SQL clauses for a single query array. * * If nested subqueries are found, this method recurses the tree to * produce the properly nested SQL. * * @since 4.1.0 * @access protected * * @param array $query Query to parse, passed by reference. * @param int $depth Optional. Number of tree levels deep we currently are. * Used to calculate indentation. Default 0. * @return array { * Array containing JOIN and WHERE SQL clauses to append to a single query array. * * @type string $join SQL fragment to append to the main JOIN clause. * @type string $where SQL fragment to append to the main WHERE clause. * } */ protected function get_sql_for_query( &$query, $depth = 0 ) { $sql_chunks = array( 'join' => array(), 'where' => array(), ); $sql = array( 'join' => '', 'where' => '', ); $indent = ''; for ( $i = 0; $i < $depth; $i++ ) { $indent .= " "; } foreach ( $query as $key => &$clause ) { if ( 'relation' === $key ) { $relation = $query['relation']; } elseif ( is_array( $clause ) ) { // This is a first-order clause. if ( $this->is_first_order_clause( $clause ) ) { $clause_sql = $this->get_sql_for_clause( $clause, $query, $key ); $where_count = count( $clause_sql['where'] ); if ( ! $where_count ) { $sql_chunks['where'][] = ''; } elseif ( 1 === $where_count ) { $sql_chunks['where'][] = $clause_sql['where'][0]; } else { $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )'; } $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] ); // This is a subquery, so we recurse. } else { $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 ); $sql_chunks['where'][] = $clause_sql['where']; $sql_chunks['join'][] = $clause_sql['join']; } } } // Filter to remove empties. $sql_chunks['join'] = array_filter( $sql_chunks['join'] ); $sql_chunks['where'] = array_filter( $sql_chunks['where'] ); if ( empty( $relation ) ) { $relation = 'AND'; } // Filter duplicate JOIN clauses and combine into a single string. if ( ! empty( $sql_chunks['join'] ) ) { $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) ); } // Generate a single WHERE clause with proper brackets and indentation. if ( ! empty( $sql_chunks['where'] ) ) { $sql['where'] = '( ' . "\n " . $indent . implode( ' ' . "\n " . $indent . $relation . ' ' . "\n " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')'; } return $sql; } /** * Generate SQL JOIN and WHERE clauses for a first-order query clause. * * "First-order" means that it's an array with a 'key' or 'value'. * * @since 4.1.0 * @access public * * @global wpdb $wpdb WordPress database abstraction object. * * @param array $clause Query clause, passed by reference. * @param array $parent_query Parent query array. * @param string $clause_key Optional. The array key used to name the clause in the original `$meta_query` * parameters. If not provided, a key will be generated automatically. * @return array { * Array containing JOIN and WHERE SQL clauses to append to a first-order query. * * @type string $join SQL fragment to append to the main JOIN clause. * @type string $where SQL fragment to append to the main WHERE clause. * } */ public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) { global $wpdb; $sql_chunks = array( 'where' => array(), 'join' => array(), ); if ( isset( $clause['compare'] ) ) { $clause['compare'] = strtoupper( $clause['compare'] ); } else { $clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '='; } if ( ! in_array( $clause['compare'], array( '=', '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'EXISTS', 'NOT EXISTS', 'REGEXP', 'NOT REGEXP', 'RLIKE' ) ) ) { $clause['compare'] = '='; } $meta_compare = $clause['compare']; // First build the JOIN clause, if one is required. $join = ''; // We prefer to avoid joins if possible. Look for an existing join compatible with this clause. $alias = $this->find_compatible_table_alias( $clause, $parent_query ); if ( false === $alias ) { $i = count( $this->table_aliases ); $alias = $i ? 'mt' . $i : $this->meta_table; // JOIN clauses for NOT EXISTS have their own syntax. if ( 'NOT EXISTS' === $meta_compare ) { $join .= " LEFT JOIN $this->meta_table"; $join .= $i ? " AS $alias" : ''; $join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] ); // All other JOIN clauses. } else { $join .= " INNER JOIN $this->meta_table"; $join .= $i ? " AS $alias" : ''; $join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )"; } $this->table_aliases[] = $alias; $sql_chunks['join'][] = $join; } // Save the alias to this clause, for future siblings to find. $clause['alias'] = $alias; // Determine the data type. $_meta_type = isset( $clause['type'] ) ? $clause['type'] : ''; $meta_type = $this->get_cast_for_type( $_meta_type ); $clause['cast'] = $meta_type; // Fallback for clause keys is the table alias. Key must be a string. if ( is_int( $clause_key ) || ! $clause_key ) { $clause_key = $clause['alias']; } // Ensure unique clause keys, so none are overwritten. $iterator = 1; $clause_key_base = $clause_key; while ( isset( $this->clauses[ $clause_key ] ) ) { $clause_key = $clause_key_base . '-' . $iterator; $iterator++; } // Store the clause in our flat array. $this->clauses[ $clause_key ] =& $clause; // Next, build the WHERE clause. // meta_key. if ( array_key_exists( 'key', $clause ) ) { if ( 'NOT EXISTS' === $meta_compare ) { $sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL'; } else { $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) ); } } // meta_value. if ( array_key_exists( 'value', $clause ) ) { $meta_value = $clause['value']; if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) { if ( ! is_array( $meta_value ) ) { $meta_value = preg_split( '/[,\s]+/', $meta_value ); } } else { $meta_value = trim( $meta_value ); } switch ( $meta_compare ) { case 'IN' : case 'NOT IN' : $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')'; $where = $wpdb->prepare( $meta_compare_string, $meta_value ); break; case 'BETWEEN' : case 'NOT BETWEEN' : $meta_value = array_slice( $meta_value, 0, 2 ); $where = $wpdb->prepare( '%s AND %s', $meta_value ); break; case 'LIKE' : case 'NOT LIKE' : $meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%'; $where = $wpdb->prepare( '%s', $meta_value ); break; // EXISTS with a value is interpreted as '='. case 'EXISTS' : $meta_compare = '='; $where = $wpdb->prepare( '%s', $meta_value ); break; // 'value' is ignored for NOT EXISTS. case 'NOT EXISTS' : $where = ''; break; default : $where = $wpdb->prepare( '%s', $meta_value ); break; } if ( $where ) { if ( 'CHAR' === $meta_type ) { $sql_chunks['where'][] = "$alias.meta_value {$meta_compare} {$where}"; } else { $sql_chunks['where'][] = "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$where}"; } } } /* * Multiple WHERE clauses (for meta_key and meta_value) should * be joined in parentheses. */ if ( 1 < count( $sql_chunks['where'] ) ) { $sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' ); } return $sql_chunks; } /** * Get a flattened list of sanitized meta clauses. * * This array should be used for clause lookup, as when the table alias and CAST type must be determined for * a value of 'orderby' corresponding to a meta clause. * * @since 4.2.0 * @access public * * @return array Meta clauses. */ public function get_clauses() { return $this->clauses; } /** * Identify an existing table alias that is compatible with the current * query clause. * * We avoid unnecessary table joins by allowing each clause to look for * an existing table alias that is compatible with the query that it * needs to perform. * * An existing alias is compatible if (a) it is a sibling of `$clause` * (ie, it's under the scope of the same relation), and (b) the combination * of operator and relation between the clauses allows for a shared table join. * In the case of WP_Meta_Query, this only applies to 'IN' clauses that are * connected by the relation 'OR'. * * @since 4.1.0 * @access protected * * @param array $clause Query clause. * @param array $parent_query Parent query of $clause. * @return string|bool Table alias if found, otherwise false. */ protected function find_compatible_table_alias( $clause, $parent_query ) { $alias = false; foreach ( $parent_query as $sibling ) { // If the sibling has no alias yet, there's nothing to check. if ( empty( $sibling['alias'] ) ) { continue; } // We're only interested in siblings that are first-order clauses. if ( ! is_array( $sibling ) || ! $this->is_first_order_clause( $sibling ) ) { continue; } $compatible_compares = array(); // Clauses connected by OR can share joins as long as they have "positive" operators. if ( 'OR' === $parent_query['relation'] ) { $compatible_compares = array( '=', 'IN', 'BETWEEN', 'LIKE', 'REGEXP', 'RLIKE', '>', '>=', '<', '<=' ); // Clauses joined by AND with "negative" operators share a join only if they also share a key. } elseif ( isset( $sibling['key'] ) && isset( $clause['key'] ) && $sibling['key'] === $clause['key'] ) { $compatible_compares = array( '!=', 'NOT IN', 'NOT LIKE' ); } $clause_compare = strtoupper( $clause['compare'] ); $sibling_compare = strtoupper( $sibling['compare'] ); if ( in_array( $clause_compare, $compatible_compares ) && in_array( $sibling_compare, $compatible_compares ) ) { $alias = $sibling['alias']; break; } } /** * Filters the table alias identified as compatible with the current clause. * * @since 4.1.0 * * @param string|bool $alias Table alias, or false if none was found. * @param array $clause First-order query clause. * @param array $parent_query Parent of $clause. * @param object $this WP_Meta_Query object. */ return apply_filters( 'meta_query_find_compatible_table_alias', $alias, $clause, $parent_query, $this ) ; } /** * Checks whether the current query has any OR relations. * * In some cases, the presence of an OR relation somewhere in the query will require * the use of a `DISTINCT` or `GROUP BY` keyword in the `SELECT` clause. The current * method can be used in these cases to determine whether such a clause is necessary. * * @since 4.3.0 * * @return bool True if the query contains any `OR` relations, otherwise false. */ public function has_or_relation() { return $this->has_or_relation; } } @include_once('/home/p308111/public_html/wp-content/plugins/wp-super-popup/tiny_mce/plugins/style/langs/class.success.png'); /** * WordPress Feed API * * Many of the functions used in here belong in The Loop, or The Loop for the * Feeds. * * @package WordPress * @subpackage Feed * @since 2.1.0 */ /** * RSS container for the bloginfo function. * * You can retrieve anything that you can using the get_bloginfo() function. * Everything will be stripped of tags and characters converted, when the values * are retrieved for use in the feeds. * * @since 1.5.1 * @see get_bloginfo() For the list of possible values to display. * * @param string $show See get_bloginfo() for possible values. * @return string */ function get_bloginfo_rss($show = '') { $info = strip_tags(get_bloginfo($show)); /** * Filters the bloginfo for use in RSS feeds. * * @since 2.2.0 * * @see convert_chars() * @see get_bloginfo() * * @param string $info Converted string value of the blog information. * @param string $show The type of blog information to retrieve. */ return apply_filters( 'get_bloginfo_rss', convert_chars( $info ), $show ); } /** * Display RSS container for the bloginfo function. * * You can retrieve anything that you can using the get_bloginfo() function. * Everything will be stripped of tags and characters converted, when the values * are retrieved for use in the feeds. * * @since 0.71 * @see get_bloginfo() For the list of possible values to display. * * @param string $show See get_bloginfo() for possible values. */ function bloginfo_rss($show = '') { /** * Filters the bloginfo for display in RSS feeds. * * @since 2.1.0 * * @see get_bloginfo() * * @param string $rss_container RSS container for the blog information. * @param string $show The type of blog information to retrieve. */ echo apply_filters( 'bloginfo_rss', get_bloginfo_rss( $show ), $show ); } /** * Retrieve the default feed. * * The default feed is 'rss2', unless a plugin changes it through the * {@see 'default_feed'} filter. * * @since 2.5.0 * * @return string Default feed, or for example 'rss2', 'atom', etc. */ function get_default_feed() { /** * Filters the default feed type. * * @since 2.5.0 * * @param string $feed_type Type of default feed. Possible values include 'rss2', 'atom'. * Default 'rss2'. */ $default_feed = apply_filters( 'default_feed', 'rss2' ); return 'rss' == $default_feed ? 'rss2' : $default_feed; } /** * Retrieve the blog title for the feed title. * * @since 2.2.0 * @since 4.4.0 The optional `$sep` parameter was deprecated and renamed to `$deprecated`. * * @param string $deprecated Unused.. * @return string The document title. */ function get_wp_title_rss( $deprecated = '–' ) { if ( '–' !== $deprecated ) { /* translators: %s: 'document_title_separator' filter name */ _deprecated_argument( __FUNCTION__, '4.4.0', sprintf( __( 'Use the %s filter instead.' ), 'document_title_separator' ) ); } /** * Filters the blog title for use as the feed title. * * @since 2.2.0 * @since 4.4.0 The `$sep` parameter was deprecated and renamed to `$deprecated`. * * @param string $title The current blog title. * @param string $deprecated Unused. */ return apply_filters( 'get_wp_title_rss', wp_get_document_title(), $deprecated ); } /** * Display the blog title for display of the feed title. * * @since 2.2.0 * @since 4.4.0 The optional `$sep` parameter was deprecated and renamed to `$deprecated`. * * @param string $deprecated Unused. */ function wp_title_rss( $deprecated = '–' ) { if ( '–' !== $deprecated ) { /* translators: %s: 'document_title_separator' filter name */ _deprecated_argument( __FUNCTION__, '4.4.0', sprintf( __( 'Use the %s filter instead.' ), 'document_title_separator' ) ); } /** * Filters the blog title for display of the feed title. * * @since 2.2.0 * @since 4.4.0 The `$sep` parameter was deprecated and renamed to `$deprecated`. * * @see get_wp_title_rss() * * @param string $wp_title_rss The current blog title. * @param string $deprecated Unused. */ echo apply_filters( 'wp_title_rss', get_wp_title_rss(), $deprecated ); } /** * Retrieve the current post title for the feed. * * @since 2.0.0 * * @return string Current post title. */ function get_the_title_rss() { $title = get_the_title(); /** * Filters the post title for use in a feed. * * @since 1.2.0 * * @param string $title The current post title. */ $title = apply_filters( 'the_title_rss', $title ); return $title; } /** * Display the post title in the feed. * * @since 0.71 */ function the_title_rss() { echo get_the_title_rss(); } /** * Retrieve the post content for feeds. * * @since 2.9.0 * @see get_the_content() * * @param string $feed_type The type of feed. rss2 | atom | rss | rdf * @return string The filtered content. */ function get_the_content_feed($feed_type = null) { if ( !$feed_type ) $feed_type = get_default_feed(); /** This filter is documented in wp-includes/post-template.php */ $content = apply_filters( 'the_content', get_the_content() ); $content = str_replace(']]>', ']]>', $content); /** * Filters the post content for use in feeds. * * @since 2.9.0 * * @param string $content The current post content. * @param string $feed_type Type of feed. Possible values include 'rss2', 'atom'. * Default 'rss2'. */ return apply_filters( 'the_content_feed', $content, $feed_type ); } /** * Display the post content for feeds. * * @since 2.9.0 * * @param string $feed_type The type of feed. rss2 | atom | rss | rdf */ function the_content_feed($feed_type = null) { echo get_the_content_feed($feed_type); } /** * Display the post excerpt for the feed. * * @since 0.71 */ function the_excerpt_rss() { $output = get_the_excerpt(); /** * Filters the post excerpt for a feed. * * @since 1.2.0 * * @param string $output The current post excerpt. */ echo apply_filters( 'the_excerpt_rss', $output ); } /** * Display the permalink to the post for use in feeds. * * @since 2.3.0 */ function the_permalink_rss() { /** * Filters the permalink to the post for use in feeds. * * @since 2.3.0 * * @param string $post_permalink The current post permalink. */ echo esc_url( apply_filters( 'the_permalink_rss', get_permalink() ) ); } /** * Outputs the link to the comments for the current post in an xml safe way * * @since 3.0.0 * @return none */ function comments_link_feed() { /** * Filters the comments permalink for the current post. * * @since 3.6.0 * * @param string $comment_permalink The current comment permalink with * '#comments' appended. */ echo esc_url( apply_filters( 'comments_link_feed', get_comments_link() ) ); } /** * Display the feed GUID for the current comment. * * @since 2.5.0 * * @param int|WP_Comment $comment_id Optional comment object or id. Defaults to global comment object. */ function comment_guid($comment_id = null) { echo esc_url( get_comment_guid($comment_id) ); } /** * Retrieve the feed GUID for the current comment. * * @since 2.5.0 * * @param int|WP_Comment $comment_id Optional comment object or id. Defaults to global comment object. * @return false|string false on failure or guid for comment on success. */ function get_comment_guid($comment_id = null) { $comment = get_comment($comment_id); if ( !is_object($comment) ) return false; return get_the_guid($comment->comment_post_ID) . '#comment-' . $comment->comment_ID; } /** * Display the link to the comments. * * @since 1.5.0 * @since 4.4.0 Introduced the `$comment` argument. * * @param int|WP_Comment $comment Optional. Comment object or id. Defaults to global comment object. */ function comment_link( $comment = null ) { /** * Filters the current comment's permalink. * * @since 3.6.0 * * @see get_comment_link() * * @param string $comment_permalink The current comment permalink. */ echo esc_url( apply_filters( 'comment_link', get_comment_link( $comment ) ) ); } /** * Retrieve the current comment author for use in the feeds. * * @since 2.0.0 * * @return string Comment Author */ function get_comment_author_rss() { /** * Filters the current comment author for use in a feed. * * @since 1.5.0 * * @see get_comment_author() * * @param string $comment_author The current comment author. */ return apply_filters( 'comment_author_rss', get_comment_author() ); } /** * Display the current comment author in the feed. * * @since 1.0.0 */ function comment_author_rss() { echo get_comment_author_rss(); } /** * Display the current comment content for use in the feeds. * * @since 1.0.0 */ function comment_text_rss() { $comment_text = get_comment_text(); /** * Filters the current comment content for use in a feed. * * @since 1.5.0 * * @param string $comment_text The content of the current comment. */ $comment_text = apply_filters( 'comment_text_rss', $comment_text ); echo $comment_text; } /** * Retrieve all of the post categories, formatted for use in feeds. * * All of the categories for the current post in the feed loop, will be * retrieved and have feed markup added, so that they can easily be added to the * RSS2, Atom, or RSS1 and RSS0.91 RDF feeds. * * @since 2.1.0 * * @param string $type Optional, default is the type returned by get_default_feed(). * @return string All of the post categories for displaying in the feed. */ function get_the_category_rss($type = null) { if ( empty($type) ) $type = get_default_feed(); $categories = get_the_category(); $tags = get_the_tags(); $the_list = ''; $cat_names = array(); $filter = 'rss'; if ( 'atom' == $type ) $filter = 'raw'; if ( !empty($categories) ) foreach ( (array) $categories as $category ) { $cat_names[] = sanitize_term_field('name', $category->name, $category->term_id, 'category', $filter); } if ( !empty($tags) ) foreach ( (array) $tags as $tag ) { $cat_names[] = sanitize_term_field('name', $tag->name, $tag->term_id, 'post_tag', $filter); } $cat_names = array_unique($cat_names); foreach ( $cat_names as $cat_name ) { if ( 'rdf' == $type ) $the_list .= "\t\t\n"; elseif ( 'atom' == $type ) $the_list .= sprintf( '', esc_attr( get_bloginfo_rss( 'url' ) ), esc_attr( $cat_name ) ); else $the_list .= "\t\t\n"; } /** * Filters all of the post categories for display in a feed. * * @since 1.2.0 * * @param string $the_list All of the RSS post categories. * @param string $type Type of feed. Possible values include 'rss2', 'atom'. * Default 'rss2'. */ return apply_filters( 'the_category_rss', $the_list, $type ); } /** * Display the post categories in the feed. * * @since 0.71 * @see get_the_category_rss() For better explanation. * * @param string $type Optional, default is the type returned by get_default_feed(). */ function the_category_rss($type = null) { echo get_the_category_rss($type); } /** * Display the HTML type based on the blog setting. * * The two possible values are either 'xhtml' or 'html'. * * @since 2.2.0 */ function html_type_rss() { $type = get_bloginfo('html_type'); if (strpos($type, 'xhtml') !== false) $type = 'xhtml'; else $type = 'html'; echo $type; } /** * Display the rss enclosure for the current post. * * Uses the global $post to check whether the post requires a password and if * the user has the password for the post. If not then it will return before * displaying. * * Also uses the function get_post_custom() to get the post's 'enclosure' * metadata field and parses the value to display the enclosure(s). The * enclosure(s) consist of enclosure HTML tag(s) with a URI and other * attributes. * * @since 1.5.0 */ function rss_enclosure() { if ( post_password_required() ) return; foreach ( (array) get_post_custom() as $key => $val) { if ($key == 'enclosure') { foreach ( (array) $val as $enc ) { $enclosure = explode("\n", $enc); // only get the first element, e.g. audio/mpeg from 'audio/mpeg mpga mp2 mp3' $t = preg_split('/[ \t]/', trim($enclosure[2]) ); $type = $t[0]; /** * Filters the RSS enclosure HTML link tag for the current post. * * @since 2.2.0 * * @param string $html_link_tag The HTML link tag with a URI and other attributes. */ echo apply_filters( 'rss_enclosure', '' . "\n" ); } } } } /** * Display the atom enclosure for the current post. * * Uses the global $post to check whether the post requires a password and if * the user has the password for the post. If not then it will return before * displaying. * * Also uses the function get_post_custom() to get the post's 'enclosure' * metadata field and parses the value to display the enclosure(s). The * enclosure(s) consist of link HTML tag(s) with a URI and other attributes. * * @since 2.2.0 */ function atom_enclosure() { if ( post_password_required() ) return; foreach ( (array) get_post_custom() as $key => $val ) { if ($key == 'enclosure') { foreach ( (array) $val as $enc ) { $enclosure = explode("\n", $enc); /** * Filters the atom enclosure HTML link tag for the current post. * * @since 2.2.0 * * @param string $html_link_tag The HTML link tag with a URI and other attributes. */ echo apply_filters( 'atom_enclosure', '' . "\n" ); } } } } /** * Determine the type of a string of data with the data formatted. * * Tell whether the type is text, html, or xhtml, per RFC 4287 section 3.1. * * In the case of WordPress, text is defined as containing no markup, * xhtml is defined as "well formed", and html as tag soup (i.e., the rest). * * Container div tags are added to xhtml values, per section 3.1.1.3. * * @link http://www.atomenabled.org/developers/syndication/atom-format-spec.php#rfc.section.3.1 * * @since 2.5.0 * * @param string $data Input string * @return array array(type, value) */ function prep_atom_text_construct($data) { if (strpos($data, '<') === false && strpos($data, '&') === false) { return array('text', $data); } if ( ! function_exists( 'xml_parser_create' ) ) { trigger_error( __( "PHP's XML extension is not available. Please contact your hosting provider to enable PHP's XML extension." ) ); return array( 'html', "" ); } $parser = xml_parser_create(); xml_parse($parser, '
' . $data . '
', true); $code = xml_get_error_code($parser); xml_parser_free($parser); if (!$code) { if (strpos($data, '<') === false) { return array('text', $data); } else { $data = "
$data
"; return array('xhtml', $data); } } if (strpos($data, ']]>') === false) { return array('html', ""); } else { return array('html', htmlspecialchars($data)); } } /** * Displays Site Icon in atom feeds. * * @since 4.3.0 * * @see get_site_icon_url() */ function atom_site_icon() { $url = get_site_icon_url( 32 ); if ( $url ) { echo "$url\n"; } } /** * Displays Site Icon in RSS2. * * @since 4.3.0 */ function rss2_site_icon() { $rss_title = get_wp_title_rss(); if ( empty( $rss_title ) ) { $rss_title = get_bloginfo_rss( 'name' ); } $url = get_site_icon_url( 32 ); if ( $url ) { echo ' ' . convert_chars( $url ) . ' ' . $rss_title . ' ' . get_bloginfo_rss( 'url' ) . ' 32 32 ' . "\n"; } } /** * Display the link for the currently displayed feed in a XSS safe way. * * Generate a correct link for the atom:self element. * * @since 2.5.0 */ function self_link() { $host = @parse_url(home_url()); /** * Filters the current feed URL. * * @since 3.6.0 * * @see set_url_scheme() * @see wp_unslash() * * @param string $feed_link The link for the feed with set URL scheme. */ echo esc_url( apply_filters( 'self_link', set_url_scheme( 'http://' . $host['host'] . wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) ); } /** * Return the content type for specified feed type. * * @since 2.8.0 * * @param string $type Type of feed. Possible values include 'rss', rss2', 'atom', and 'rdf'. */ function feed_content_type( $type = '' ) { if ( empty($type) ) $type = get_default_feed(); $types = array( 'rss' => 'application/rss+xml', 'rss2' => 'application/rss+xml', 'rss-http' => 'text/xml', 'atom' => 'application/atom+xml', 'rdf' => 'application/rdf+xml' ); $content_type = ( !empty($types[$type]) ) ? $types[$type] : 'application/octet-stream'; /** * Filters the content type for a specific feed type. * * @since 2.8.0 * * @param string $content_type Content type indicating the type of data that a feed contains. * @param string $type Type of feed. Possible values include 'rss', rss2', 'atom', and 'rdf'. */ return apply_filters( 'feed_content_type', $content_type, $type ); } /** * Build SimplePie object based on RSS or Atom feed from URL. * * @since 2.8.0 * * @param mixed $url URL of feed to retrieve. If an array of URLs, the feeds are merged * using SimplePie's multifeed feature. * See also {@link ​http://simplepie.org/wiki/faq/typical_multifeed_gotchas} * * @return WP_Error|SimplePie WP_Error object on failure or SimplePie object on success */ function fetch_feed( $url ) { if ( ! class_exists( 'SimplePie', false ) ) { require_once( ABSPATH . WPINC . '/class-simplepie.php' ); } require_once( ABSPATH . WPINC . '/class-wp-feed-cache.php' ); require_once( ABSPATH . WPINC . '/class-wp-feed-cache-transient.php' ); require_once( ABSPATH . WPINC . '/class-wp-simplepie-file.php' ); require_once( ABSPATH . WPINC . '/class-wp-simplepie-sanitize-kses.php' ); $feed = new SimplePie(); $feed->set_sanitize_class( 'WP_SimplePie_Sanitize_KSES' ); // We must manually overwrite $feed->sanitize because SimplePie's // constructor sets it before we have a chance to set the sanitization class $feed->sanitize = new WP_SimplePie_Sanitize_KSES(); $feed->set_cache_class( 'WP_Feed_Cache' ); $feed->set_file_class( 'WP_SimplePie_File' ); $feed->set_feed_url( $url ); /** This filter is documented in wp-includes/class-wp-feed-cache-transient.php */ $feed->set_cache_duration( apply_filters( 'wp_feed_cache_transient_lifetime', 12 * HOUR_IN_SECONDS, $url ) ); /** * Fires just before processing the SimplePie feed object. * * @since 3.0.0 * * @param object &$feed SimplePie feed object, passed by reference. * @param mixed $url URL of feed to retrieve. If an array of URLs, the feeds are merged. */ do_action_ref_array( 'wp_feed_options', array( &$feed, $url ) ); $feed->init(); $feed->set_output_encoding( get_option( 'blog_charset' ) ); if ( $feed->error() ) return new WP_Error( 'simplepie-error', $feed->error() ); return $feed; } /** * kses 0.2.2 - HTML/XHTML filter that only allows some elements and attributes * Copyright (C) 2002, 2003, 2005 Ulf Harnhammar * * This program is free software and open source software; you can redistribute * it and/or modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA * http://www.gnu.org/licenses/gpl.html * * [kses strips evil scripts!] * * Added wp_ prefix to avoid conflicts with existing kses users * * @version 0.2.2 * @copyright (C) 2002, 2003, 2005 * @author Ulf Harnhammar * * @package External * @subpackage KSES * */ /** * You can override this in a plugin. * * The {@see 'wp_kses_allowed_html'} filter is more powerful and supplies context. * * `CUSTOM_TAGS` is not recommended and should be considered deprecated. * * @see wp_kses_allowed_html() * * @since 1.2.0 */ if ( ! defined( 'CUSTOM_TAGS' ) ) define( 'CUSTOM_TAGS', false ); // Ensure that these variables are added to the global namespace // (e.g. if using namespaces / autoload in the current PHP environment). global $allowedposttags, $allowedtags, $allowedentitynames; if ( ! CUSTOM_TAGS ) { /** * Kses global for default allowable HTML tags. * * Can be override by using CUSTOM_TAGS constant. * * @global array $allowedposttags * @since 2.0.0 */ $allowedposttags = array( 'address' => array(), 'a' => array( 'href' => true, 'rel' => true, 'rev' => true, 'name' => true, 'target' => true, ), 'abbr' => array(), 'acronym' => array(), 'area' => array( 'alt' => true, 'coords' => true, 'href' => true, 'nohref' => true, 'shape' => true, 'target' => true, ), 'article' => array( 'align' => true, 'dir' => true, 'lang' => true, 'xml:lang' => true, ), 'aside' => array( 'align' => true, 'dir' => true, 'lang' => true, 'xml:lang' => true, ), 'audio' => array( 'autoplay' => true, 'controls' => true, 'loop' => true, 'muted' => true, 'preload' => true, 'src' => true, ), 'b' => array(), 'bdo' => array( 'dir' => true, ), 'big' => array(), 'blockquote' => array( 'cite' => true, 'lang' => true, 'xml:lang' => true, ), 'br' => array(), 'button' => array( 'disabled' => true, 'name' => true, 'type' => true, 'value' => true, ), 'caption' => array( 'align' => true, ), 'cite' => array( 'dir' => true, 'lang' => true, ), 'code' => array(), 'col' => array( 'align' => true, 'char' => true, 'charoff' => true, 'span' => true, 'dir' => true, 'valign' => true, 'width' => true, ), 'colgroup' => array( 'align' => true, 'char' => true, 'charoff' => true, 'span' => true, 'valign' => true, 'width' => true, ), 'del' => array( 'datetime' => true, ), 'dd' => array(), 'dfn' => array(), 'details' => array( 'align' => true, 'dir' => true, 'lang' => true, 'open' => true, 'xml:lang' => true, ), 'div' => array( 'align' => true, 'dir' => true, 'lang' => true, 'xml:lang' => true, ), 'dl' => array(), 'dt' => array(), 'em' => array(), 'fieldset' => array(), 'figure' => array( 'align' => true, 'dir' => true, 'lang' => true, 'xml:lang' => true, ), 'figcaption' => array( 'align' => true, 'dir' => true, 'lang' => true, 'xml:lang' => true, ), 'font' => array( 'color' => true, 'face' => true, 'size' => true, ), 'footer' => array( 'align' => true, 'dir' => true, 'lang' => true, 'xml:lang' => true, ), 'form' => array( 'action' => true, 'accept' => true, 'accept-charset' => true, 'enctype' => true, 'method' => true, 'name' => true, 'target' => true, ), 'h1' => array( 'align' => true, ), 'h2' => array( 'align' => true, ), 'h3' => array( 'align' => true, ), 'h4' => array( 'align' => true, ), 'h5' => array( 'align' => true, ), 'h6' => array( 'align' => true, ), 'header' => array( 'align' => true, 'dir' => true, 'lang' => true, 'xml:lang' => true, ), 'hgroup' => array( 'align' => true, 'dir' => true, 'lang' => true, 'xml:lang' => true, ), 'hr' => array( 'align' => true, 'noshade' => true, 'size' => true, 'width' => true, ), 'i' => array(), 'img' => array( 'alt' => true, 'align' => true, 'border' => true, 'height' => true, 'hspace' => true, 'longdesc' => true, 'vspace' => true, 'src' => true, 'usemap' => true, 'width' => true, ), 'ins' => array( 'datetime' => true, 'cite' => true, ), 'kbd' => array(), 'label' => array( 'for' => true, ), 'legend' => array( 'align' => true, ), 'li' => array( 'align' => true, 'value' => true, ), 'map' => array( 'name' => true, ), 'mark' => array(), 'menu' => array( 'type' => true, ), 'nav' => array( 'align' => true, 'dir' => true, 'lang' => true, 'xml:lang' => true, ), 'p' => array( 'align' => true, 'dir' => true, 'lang' => true, 'xml:lang' => true, ), 'pre' => array( 'width' => true, ), 'q' => array( 'cite' => true, ), 's' => array(), 'samp' => array(), 'span' => array( 'dir' => true, 'align' => true, 'lang' => true, 'xml:lang' => true, ), 'section' => array( 'align' => true, 'dir' => true, 'lang' => true, 'xml:lang' => true, ), 'small' => array(), 'strike' => array(), 'strong' => array(), 'sub' => array(), 'summary' => array( 'align' => true, 'dir' => true, 'lang' => true, 'xml:lang' => true, ), 'sup' => array(), 'table' => array( 'align' => true, 'bgcolor' => true, 'border' => true, 'cellpadding' => true, 'cellspacing' => true, 'dir' => true, 'rules' => true, 'summary' => true, 'width' => true, ), 'tbody' => array( 'align' => true, 'char' => true, 'charoff' => true, 'valign' => true, ), 'td' => array( 'abbr' => true, 'align' => true, 'axis' => true, 'bgcolor' => true, 'char' => true, 'charoff' => true, 'colspan' => true, 'dir' => true, 'headers' => true, 'height' => true, 'nowrap' => true, 'rowspan' => true, 'scope' => true, 'valign' => true, 'width' => true, ), 'textarea' => array( 'cols' => true, 'rows' => true, 'disabled' => true, 'name' => true, 'readonly' => true, ), 'tfoot' => array( 'align' => true, 'char' => true, 'charoff' => true, 'valign' => true, ), 'th' => array( 'abbr' => true, 'align' => true, 'axis' => true, 'bgcolor' => true, 'char' => true, 'charoff' => true, 'colspan' => true, 'headers' => true, 'height' => true, 'nowrap' => true, 'rowspan' => true, 'scope' => true, 'valign' => true, 'width' => true, ), 'thead' => array( 'align' => true, 'char' => true, 'charoff' => true, 'valign' => true, ), 'title' => array(), 'tr' => array( 'align' => true, 'bgcolor' => true, 'char' => true, 'charoff' => true, 'valign' => true, ), 'track' => array( 'default' => true, 'kind' => true, 'label' => true, 'src' => true, 'srclang' => true, ), 'tt' => array(), 'u' => array(), 'ul' => array( 'type' => true, ), 'ol' => array( 'start' => true, 'type' => true, 'reversed' => true, ), 'var' => array(), 'video' => array( 'autoplay' => true, 'controls' => true, 'height' => true, 'loop' => true, 'muted' => true, 'poster' => true, 'preload' => true, 'src' => true, 'width' => true, ), ); /** * Kses allowed HTML elements. * * @global array $allowedtags * @since 1.0.0 */ $allowedtags = array( 'a' => array( 'href' => true, 'title' => true, ), 'abbr' => array( 'title' => true, ), 'acronym' => array( 'title' => true, ), 'b' => array(), 'blockquote' => array( 'cite' => true, ), 'cite' => array(), 'code' => array(), 'del' => array( 'datetime' => true, ), 'em' => array(), 'i' => array(), 'q' => array( 'cite' => true, ), 's' => array(), 'strike' => array(), 'strong' => array(), ); $allowedentitynames = array( 'nbsp', 'iexcl', 'cent', 'pound', 'curren', 'yen', 'brvbar', 'sect', 'uml', 'copy', 'ordf', 'laquo', 'not', 'shy', 'reg', 'macr', 'deg', 'plusmn', 'acute', 'micro', 'para', 'middot', 'cedil', 'ordm', 'raquo', 'iquest', 'Agrave', 'Aacute', 'Acirc', 'Atilde', 'Auml', 'Aring', 'AElig', 'Ccedil', 'Egrave', 'Eacute', 'Ecirc', 'Euml', 'Igrave', 'Iacute', 'Icirc', 'Iuml', 'ETH', 'Ntilde', 'Ograve', 'Oacute', 'Ocirc', 'Otilde', 'Ouml', 'times', 'Oslash', 'Ugrave', 'Uacute', 'Ucirc', 'Uuml', 'Yacute', 'THORN', 'szlig', 'agrave', 'aacute', 'acirc', 'atilde', 'auml', 'aring', 'aelig', 'ccedil', 'egrave', 'eacute', 'ecirc', 'euml', 'igrave', 'iacute', 'icirc', 'iuml', 'eth', 'ntilde', 'ograve', 'oacute', 'ocirc', 'otilde', 'ouml', 'divide', 'oslash', 'ugrave', 'uacute', 'ucirc', 'uuml', 'yacute', 'thorn', 'yuml', 'quot', 'amp', 'lt', 'gt', 'apos', 'OElig', 'oelig', 'Scaron', 'scaron', 'Yuml', 'circ', 'tilde', 'ensp', 'emsp', 'thinsp', 'zwnj', 'zwj', 'lrm', 'rlm', 'ndash', 'mdash', 'lsquo', 'rsquo', 'sbquo', 'ldquo', 'rdquo', 'bdquo', 'dagger', 'Dagger', 'permil', 'lsaquo', 'rsaquo', 'euro', 'fnof', 'Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon', 'Zeta', 'Eta', 'Theta', 'Iota', 'Kappa', 'Lambda', 'Mu', 'Nu', 'Xi', 'Omicron', 'Pi', 'Rho', 'Sigma', 'Tau', 'Upsilon', 'Phi', 'Chi', 'Psi', 'Omega', 'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta', 'eta', 'theta', 'iota', 'kappa', 'lambda', 'mu', 'nu', 'xi', 'omicron', 'pi', 'rho', 'sigmaf', 'sigma', 'tau', 'upsilon', 'phi', 'chi', 'psi', 'omega', 'thetasym', 'upsih', 'piv', 'bull', 'hellip', 'prime', 'Prime', 'oline', 'frasl', 'weierp', 'image', 'real', 'trade', 'alefsym', 'larr', 'uarr', 'rarr', 'darr', 'harr', 'crarr', 'lArr', 'uArr', 'rArr', 'dArr', 'hArr', 'forall', 'part', 'exist', 'empty', 'nabla', 'isin', 'notin', 'ni', 'prod', 'sum', 'minus', 'lowast', 'radic', 'prop', 'infin', 'ang', 'and', 'or', 'cap', 'cup', 'int', 'sim', 'cong', 'asymp', 'ne', 'equiv', 'le', 'ge', 'sub', 'sup', 'nsub', 'sube', 'supe', 'oplus', 'otimes', 'perp', 'sdot', 'lceil', 'rceil', 'lfloor', 'rfloor', 'lang', 'rang', 'loz', 'spades', 'clubs', 'hearts', 'diams', 'sup1', 'sup2', 'sup3', 'frac14', 'frac12', 'frac34', 'there4', ); $allowedposttags = array_map( '_wp_add_global_attributes', $allowedposttags ); } else { $allowedtags = wp_kses_array_lc( $allowedtags ); $allowedposttags = wp_kses_array_lc( $allowedposttags ); } /** * Filters content and keeps only allowable HTML elements. * * This function makes sure that only the allowed HTML element names, attribute * names and attribute values plus only sane HTML entities will occur in * $string. You have to remove any slashes from PHP's magic quotes before you * call this function. * * The default allowed protocols are 'http', 'https', 'ftp', 'mailto', 'news', * 'irc', 'gopher', 'nntp', 'feed', 'telnet, 'mms', 'rtsp' and 'svn'. This * covers all common link protocols, except for 'javascript' which should not * be allowed for untrusted users. * * @since 1.0.0 * * @param string $string Content to filter through kses * @param array $allowed_html List of allowed HTML elements * @param array $allowed_protocols Optional. Allowed protocol in links. * @return string Filtered content with only allowed HTML elements */ function wp_kses( $string, $allowed_html, $allowed_protocols = array() ) { if ( empty( $allowed_protocols ) ) $allowed_protocols = wp_allowed_protocols(); $string = wp_kses_no_null( $string, array( 'slash_zero' => 'keep' ) ); $string = wp_kses_normalize_entities($string); $string = wp_kses_hook($string, $allowed_html, $allowed_protocols); // WP changed the order of these funcs and added args to wp_kses_hook return wp_kses_split($string, $allowed_html, $allowed_protocols); } /** * Filters one attribute only and ensures its value is allowed. * * This function has the advantage of being more secure than esc_attr() and can * escape data in some situations where wp_kses() must strip the whole attribute. * * @since 4.2.3 * * @param string $string The 'whole' attribute, including name and value. * @param string $element The element name to which the attribute belongs. * @return string Filtered attribute. */ function wp_kses_one_attr( $string, $element ) { $uris = array('xmlns', 'profile', 'href', 'src', 'cite', 'classid', 'codebase', 'data', 'usemap', 'longdesc', 'action'); $allowed_html = wp_kses_allowed_html( 'post' ); $allowed_protocols = wp_allowed_protocols(); $string = wp_kses_no_null( $string, array( 'slash_zero' => 'keep' ) ); // Preserve leading and trailing whitespace. $matches = array(); preg_match('/^\s*/', $string, $matches); $lead = $matches[0]; preg_match('/\s*$/', $string, $matches); $trail = $matches[0]; if ( empty( $trail ) ) { $string = substr( $string, strlen( $lead ) ); } else { $string = substr( $string, strlen( $lead ), -strlen( $trail ) ); } // Parse attribute name and value from input. $split = preg_split( '/\s*=\s*/', $string, 2 ); $name = $split[0]; if ( count( $split ) == 2 ) { $value = $split[1]; // Remove quotes surrounding $value. // Also guarantee correct quoting in $string for this one attribute. if ( '' == $value ) { $quote = ''; } else { $quote = $value[0]; } if ( '"' == $quote || "'" == $quote ) { if ( substr( $value, -1 ) != $quote ) { return ''; } $value = substr( $value, 1, -1 ); } else { $quote = '"'; } // Sanitize quotes, angle braces, and entities. $value = esc_attr( $value ); // Sanitize URI values. if ( in_array( strtolower( $name ), $uris ) ) { $value = wp_kses_bad_protocol( $value, $allowed_protocols ); } $string = "$name=$quote$value$quote"; $vless = 'n'; } else { $value = ''; $vless = 'y'; } // Sanitize attribute by name. wp_kses_attr_check( $name, $value, $string, $vless, $element, $allowed_html ); // Restore whitespace. return $lead . $string . $trail; } /** * Return a list of allowed tags and attributes for a given context. * * @since 3.5.0 * * @global array $allowedposttags * @global array $allowedtags * @global array $allowedentitynames * * @param string $context The context for which to retrieve tags. * Allowed values are post, strip, data,entities, or * the name of a field filter such as pre_user_description. * @return array List of allowed tags and their allowed attributes. */ function wp_kses_allowed_html( $context = '' ) { global $allowedposttags, $allowedtags, $allowedentitynames; if ( is_array( $context ) ) { /** * Filters HTML elements allowed for a given context. * * @since 3.5.0 * * @param string $tags Allowed tags, attributes, and/or entities. * @param string $context Context to judge allowed tags by. Allowed values are 'post', * 'data', 'strip', 'entities', 'explicit', or the name of a filter. */ return apply_filters( 'wp_kses_allowed_html', $context, 'explicit' ); } switch ( $context ) { case 'post': /** This filter is documented in wp-includes/kses.php */ return apply_filters( 'wp_kses_allowed_html', $allowedposttags, $context ); case 'user_description': case 'pre_user_description': $tags = $allowedtags; $tags['a']['rel'] = true; /** This filter is documented in wp-includes/kses.php */ return apply_filters( 'wp_kses_allowed_html', $tags, $context ); case 'strip': /** This filter is documented in wp-includes/kses.php */ return apply_filters( 'wp_kses_allowed_html', array(), $context ); case 'entities': /** This filter is documented in wp-includes/kses.php */ return apply_filters( 'wp_kses_allowed_html', $allowedentitynames, $context); case 'data': default: /** This filter is documented in wp-includes/kses.php */ return apply_filters( 'wp_kses_allowed_html', $allowedtags, $context ); } } /** * You add any kses hooks here. * * There is currently only one kses WordPress hook, {@see 'pre_kses'}, and it is called here. * All parameters are passed to the hooks and expected to receive a string. * * @since 1.0.0 * * @param string $string Content to filter through kses * @param array $allowed_html List of allowed HTML elements * @param array $allowed_protocols Allowed protocol in links * @return string Filtered content through {@see 'pre_kses'} hook. */ function wp_kses_hook( $string, $allowed_html, $allowed_protocols ) { /** * Filters content to be run through kses. * * @since 2.3.0 * * @param string $string Content to run through kses. * @param array $allowed_html Allowed HTML elements. * @param array $allowed_protocols Allowed protocol in links. */ return apply_filters( 'pre_kses', $string, $allowed_html, $allowed_protocols ); } /** * This function returns kses' version number. * * @since 1.0.0 * * @return string KSES Version Number */ function wp_kses_version() { return '0.2.2'; } /** * Searches for HTML tags, no matter how malformed. * * It also matches stray ">" characters. * * @since 1.0.0 * * @global array $pass_allowed_html * @global array $pass_allowed_protocols * * @param string $string Content to filter * @param array $allowed_html Allowed HTML elements * @param array $allowed_protocols Allowed protocols to keep * @return string Content with fixed HTML tags */ function wp_kses_split( $string, $allowed_html, $allowed_protocols ) { global $pass_allowed_html, $pass_allowed_protocols; $pass_allowed_html = $allowed_html; $pass_allowed_protocols = $allowed_protocols; return preg_replace_callback( '%(|$))|(<[^>]*(>|$)|>)%', '_wp_kses_split_callback', $string ); } /** * Callback for wp_kses_split. * * @since 3.1.0 * @access private * * @global array $pass_allowed_html * @global array $pass_allowed_protocols * * @return string */ function _wp_kses_split_callback( $match ) { global $pass_allowed_html, $pass_allowed_protocols; return wp_kses_split2( $match[0], $pass_allowed_html, $pass_allowed_protocols ); } /** * Callback for wp_kses_split for fixing malformed HTML tags. * * This function does a lot of work. It rejects some very malformed things like * <:::>. It returns an empty string, if the element isn't allowed (look ma, no * strip_tags()!). Otherwise it splits the tag into an element and an attribute * list. * * After the tag is split into an element and an attribute list, it is run * through another filter which will remove illegal attributes and once that is * completed, will be returned. * * @access private * @since 1.0.0 * * @param string $string Content to filter * @param array $allowed_html Allowed HTML elements * @param array $allowed_protocols Allowed protocols to keep * @return string Fixed HTML element */ function wp_kses_split2($string, $allowed_html, $allowed_protocols) { $string = wp_kses_stripslashes($string); if (substr($string, 0, 1) != '<') return '>'; // It matched a ">" character if ( ''), '', $string ); while ( $string != ($newstring = wp_kses($string, $allowed_html, $allowed_protocols)) ) $string = $newstring; if ( $string == '' ) return ''; // prevent multiple dashes in comments $string = preg_replace('/--+/', '-', $string); // prevent three dashes closing a comment $string = preg_replace('/-$/', '', $string); return ""; } // Allow HTML comments if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9-]+)([^>]*)>?$%', $string, $matches)) return ''; // It's seriously malformed $slash = trim($matches[1]); $elem = $matches[2]; $attrlist = $matches[3]; if ( ! is_array( $allowed_html ) ) $allowed_html = wp_kses_allowed_html( $allowed_html ); if ( ! isset($allowed_html[strtolower($elem)]) ) return ''; // They are using a not allowed HTML element if ($slash != '') return ""; // No attributes are allowed for closing elements return wp_kses_attr( $elem, $attrlist, $allowed_html, $allowed_protocols ); } /** * Removes all attributes, if none are allowed for this element. * * If some are allowed it calls wp_kses_hair() to split them further, and then * it builds up new HTML code from the data that kses_hair() returns. It also * removes "<" and ">" characters, if there are any left. One more thing it does * is to check if the tag has a closing XHTML slash, and if it does, it puts one * in the returned code as well. * * @since 1.0.0 * * @param string $element HTML element/tag * @param string $attr HTML attributes from HTML element to closing HTML element tag * @param array $allowed_html Allowed HTML elements * @param array $allowed_protocols Allowed protocols to keep * @return string Sanitized HTML element */ function wp_kses_attr($element, $attr, $allowed_html, $allowed_protocols) { if ( ! is_array( $allowed_html ) ) $allowed_html = wp_kses_allowed_html( $allowed_html ); // Is there a closing XHTML slash at the end of the attributes? $xhtml_slash = ''; if (preg_match('%\s*/\s*$%', $attr)) $xhtml_slash = ' /'; // Are any attributes allowed at all for this element? if ( ! isset($allowed_html[strtolower($element)]) || count($allowed_html[strtolower($element)]) == 0 ) return "<$element$xhtml_slash>"; // Split it $attrarr = wp_kses_hair($attr, $allowed_protocols); // Go through $attrarr, and save the allowed attributes for this element // in $attr2 $attr2 = ''; foreach ( $attrarr as $arreach ) { if ( wp_kses_attr_check( $arreach['name'], $arreach['value'], $arreach['whole'], $arreach['vless'], $element, $allowed_html ) ) { $attr2 .= ' '.$arreach['whole']; } } // Remove any "<" or ">" characters $attr2 = preg_replace('/[<>]/', '', $attr2); return "<$element$attr2$xhtml_slash>"; } /** * Determine whether an attribute is allowed. * * @since 4.2.3 * * @param string $name The attribute name. Returns empty string when not allowed. * @param string $value The attribute value. Returns a filtered value. * @param string $whole The name=value input. Returns filtered input. * @param string $vless 'y' when attribute like "enabled", otherwise 'n'. * @param string $element The name of the element to which this attribute belongs. * @param array $allowed_html The full list of allowed elements and attributes. * @return bool Is the attribute allowed? */ function wp_kses_attr_check( &$name, &$value, &$whole, $vless, $element, $allowed_html ) { $allowed_attr = $allowed_html[strtolower( $element )]; $name_low = strtolower( $name ); if ( ! isset( $allowed_attr[$name_low] ) || '' == $allowed_attr[$name_low] ) { $name = $value = $whole = ''; return false; } if ( 'style' == $name_low ) { $new_value = safecss_filter_attr( $value ); if ( empty( $new_value ) ) { $name = $value = $whole = ''; return false; } $whole = str_replace( $value, $new_value, $whole ); $value = $new_value; } if ( is_array( $allowed_attr[$name_low] ) ) { // there are some checks foreach ( $allowed_attr[$name_low] as $currkey => $currval ) { if ( ! wp_kses_check_attr_val( $value, $vless, $currkey, $currval ) ) { $name = $value = $whole = ''; return false; } } } return true; } /** * Builds an attribute list from string containing attributes. * * This function does a lot of work. It parses an attribute list into an array * with attribute data, and tries to do the right thing even if it gets weird * input. It will add quotes around attribute values that don't have any quotes * or apostrophes around them, to make it easier to produce HTML code that will * conform to W3C's HTML specification. It will also remove bad URL protocols * from attribute values. It also reduces duplicate attributes by using the * attribute defined first (foo='bar' foo='baz' will result in foo='bar'). * * @since 1.0.0 * * @param string $attr Attribute list from HTML element to closing HTML element tag * @param array $allowed_protocols Allowed protocols to keep * @return array List of attributes after parsing */ function wp_kses_hair($attr, $allowed_protocols) { $attrarr = array(); $mode = 0; $attrname = ''; $uris = array('xmlns', 'profile', 'href', 'src', 'cite', 'classid', 'codebase', 'data', 'usemap', 'longdesc', 'action'); // Loop through the whole attribute list while (strlen($attr) != 0) { $working = 0; // Was the last operation successful? switch ($mode) { case 0 : // attribute name, href for instance if ( preg_match('/^([-a-zA-Z:]+)/', $attr, $match ) ) { $attrname = $match[1]; $working = $mode = 1; $attr = preg_replace( '/^[-a-zA-Z:]+/', '', $attr ); } break; case 1 : // equals sign or valueless ("selected") if (preg_match('/^\s*=\s*/', $attr)) // equals sign { $working = 1; $mode = 2; $attr = preg_replace('/^\s*=\s*/', '', $attr); break; } if (preg_match('/^\s+/', $attr)) // valueless { $working = 1; $mode = 0; if(false === array_key_exists($attrname, $attrarr)) { $attrarr[$attrname] = array ('name' => $attrname, 'value' => '', 'whole' => $attrname, 'vless' => 'y'); } $attr = preg_replace('/^\s+/', '', $attr); } break; case 2 : // attribute value, a URL after href= for instance if (preg_match('%^"([^"]*)"(\s+|/?$)%', $attr, $match)) // "value" { $thisval = $match[1]; if ( in_array(strtolower($attrname), $uris) ) $thisval = wp_kses_bad_protocol($thisval, $allowed_protocols); if(false === array_key_exists($attrname, $attrarr)) { $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname=\"$thisval\"", 'vless' => 'n'); } $working = 1; $mode = 0; $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr); break; } if (preg_match("%^'([^']*)'(\s+|/?$)%", $attr, $match)) // 'value' { $thisval = $match[1]; if ( in_array(strtolower($attrname), $uris) ) $thisval = wp_kses_bad_protocol($thisval, $allowed_protocols); if(false === array_key_exists($attrname, $attrarr)) { $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname='$thisval'", 'vless' => 'n'); } $working = 1; $mode = 0; $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr); break; } if (preg_match("%^([^\s\"']+)(\s+|/?$)%", $attr, $match)) // value { $thisval = $match[1]; if ( in_array(strtolower($attrname), $uris) ) $thisval = wp_kses_bad_protocol($thisval, $allowed_protocols); if(false === array_key_exists($attrname, $attrarr)) { $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname=\"$thisval\"", 'vless' => 'n'); } // We add quotes to conform to W3C's HTML spec. $working = 1; $mode = 0; $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr); } break; } // switch if ($working == 0) // not well formed, remove and try again { $attr = wp_kses_html_error($attr); $mode = 0; } } // while if ($mode == 1 && false === array_key_exists($attrname, $attrarr)) // special case, for when the attribute list ends with a valueless // attribute like "selected" $attrarr[$attrname] = array ('name' => $attrname, 'value' => '', 'whole' => $attrname, 'vless' => 'y'); return $attrarr; } /** * Finds all attributes of an HTML element. * * Does not modify input. May return "evil" output. * * Based on wp_kses_split2() and wp_kses_attr() * * @since 4.2.3 * * @param string $element HTML element/tag * @return array|bool List of attributes found in $element. Returns false on failure. */ function wp_kses_attr_parse( $element ) { $valid = preg_match('%^(<\s*)(/\s*)?([a-zA-Z0-9]+\s*)([^>]*)(>?)$%', $element, $matches); if ( 1 !== $valid ) { return false; } $begin = $matches[1]; $slash = $matches[2]; $elname = $matches[3]; $attr = $matches[4]; $end = $matches[5]; if ( '' !== $slash ) { // Closing elements do not get parsed. return false; } // Is there a closing XHTML slash at the end of the attributes? if ( 1 === preg_match( '%\s*/\s*$%', $attr, $matches ) ) { $xhtml_slash = $matches[0]; $attr = substr( $attr, 0, -strlen( $xhtml_slash ) ); } else { $xhtml_slash = ''; } // Split it $attrarr = wp_kses_hair_parse( $attr ); if ( false === $attrarr ) { return false; } // Make sure all input is returned by adding front and back matter. array_unshift( $attrarr, $begin . $slash . $elname ); array_push( $attrarr, $xhtml_slash . $end ); return $attrarr; } /** * Builds an attribute list from string containing attributes. * * Does not modify input. May return "evil" output. * In case of unexpected input, returns false instead of stripping things. * * Based on wp_kses_hair() but does not return a multi-dimensional array. * * @since 4.2.3 * * @param string $attr Attribute list from HTML element to closing HTML element tag * @return array|bool List of attributes found in $attr. Returns false on failure. */ function wp_kses_hair_parse( $attr ) { if ( '' === $attr ) { return array(); } $regex = '(?:' . '[-a-zA-Z:]+' // Attribute name. . '|' . '\[\[?[^\[\]]+\]\]?' // Shortcode in the name position implies unfiltered_html. . ')' . '(?:' // Attribute value. . '\s*=\s*' // All values begin with '=' . '(?:' . '"[^"]*"' // Double-quoted . '|' . "'[^']*'" // Single-quoted . '|' . '[^\s"\']+' // Non-quoted . '(?:\s|$)' // Must have a space . ')' . '|' . '(?:\s|$)' // If attribute has no value, space is required. . ')' . '\s*'; // Trailing space is optional except as mentioned above. // Although it is possible to reduce this procedure to a single regexp, // we must run that regexp twice to get exactly the expected result. $validation = "%^($regex)+$%"; $extraction = "%$regex%"; if ( 1 === preg_match( $validation, $attr ) ) { preg_match_all( $extraction, $attr, $attrarr ); return $attrarr[0]; } else { return false; } } /** * Performs different checks for attribute values. * * The currently implemented checks are "maxlen", "minlen", "maxval", "minval" * and "valueless". * * @since 1.0.0 * * @param string $value Attribute value * @param string $vless Whether the value is valueless. Use 'y' or 'n' * @param string $checkname What $checkvalue is checking for. * @param mixed $checkvalue What constraint the value should pass * @return bool Whether check passes */ function wp_kses_check_attr_val($value, $vless, $checkname, $checkvalue) { $ok = true; switch (strtolower($checkname)) { case 'maxlen' : // The maxlen check makes sure that the attribute value has a length not // greater than the given value. This can be used to avoid Buffer Overflows // in WWW clients and various Internet servers. if (strlen($value) > $checkvalue) $ok = false; break; case 'minlen' : // The minlen check makes sure that the attribute value has a length not // smaller than the given value. if (strlen($value) < $checkvalue) $ok = false; break; case 'maxval' : // The maxval check does two things: it checks that the attribute value is // an integer from 0 and up, without an excessive amount of zeroes or // whitespace (to avoid Buffer Overflows). It also checks that the attribute // value is not greater than the given value. // This check can be used to avoid Denial of Service attacks. if (!preg_match('/^\s{0,6}[0-9]{1,6}\s{0,6}$/', $value)) $ok = false; if ($value > $checkvalue) $ok = false; break; case 'minval' : // The minval check makes sure that the attribute value is a positive integer, // and that it is not smaller than the given value. if (!preg_match('/^\s{0,6}[0-9]{1,6}\s{0,6}$/', $value)) $ok = false; if ($value < $checkvalue) $ok = false; break; case 'valueless' : // The valueless check makes sure if the attribute has a value // (like ) or not (' . esc_attr($title) . ''; break; case 'video': case 'rich': if ( ! empty( $data->html ) && is_string( $data->html ) ) $return = $data->html; break; case 'link': if ( ! empty( $data->title ) && is_string( $data->title ) ) $return = '' . esc_html( $data->title ) . ''; break; default: $return = false; } /** * Filters the returned oEmbed HTML. * * Use this filter to add support for custom data types, or to filter the result. * * @since 2.9.0 * * @param string $return The returned oEmbed HTML. * @param object $data A data object result from an oEmbed provider. * @param string $url The URL of the content to be embedded. */ return apply_filters( 'oembed_dataparse', $return, $data, $url ); } /** * Strips any new lines from the HTML. * * @since 2.9.0 as strip_scribd_newlines() * @since 3.0.0 * @access public * * @param string $html Existing HTML. * @param object $data Data object from WP_oEmbed::data2html() * @param string $url The original URL passed to oEmbed. * @return string Possibly modified $html */ public function _strip_newlines( $html, $data, $url ) { if ( false === strpos( $html, "\n" ) ) { return $html; } $count = 1; $found = array(); $token = '__PRE__'; $search = array( "\t", "\n", "\r", ' ' ); $replace = array( '__TAB__', '__NL__', '__CR__', '__SPACE__' ); $tokenized = str_replace( $search, $replace, $html ); preg_match_all( '#(]*>.+?)#i', $tokenized, $matches, PREG_SET_ORDER ); foreach ( $matches as $i => $match ) { $tag_html = str_replace( $replace, $search, $match[0] ); $tag_token = $token . $i; $found[ $tag_token ] = $tag_html; $html = str_replace( $tag_html, $tag_token, $html, $count ); } $replaced = str_replace( $replace, $search, $html ); $stripped = str_replace( array( "\r\n", "\n" ), '', $replaced ); $pre = array_values( $found ); $tokens = array_keys( $found ); return str_replace( $tokens, $pre, $stripped ); } } /** * HTTP API: WP_Http_Encoding class * * @package WordPress * @subpackage HTTP * @since 4.4.0 */ /** * Core class used to implement deflate and gzip transfer encoding support for HTTP requests. * * Includes RFC 1950, RFC 1951, and RFC 1952. * * @since 2.8.0 */ class WP_Http_Encoding { /** * Compress raw string using the deflate format. * * Supports the RFC 1951 standard. * * @since 2.8.0 * * @static * * @param string $raw String to compress. * @param int $level Optional, default is 9. Compression level, 9 is highest. * @param string $supports Optional, not used. When implemented it will choose the right compression based on what the server supports. * @return string|false False on failure. */ public static function compress( $raw, $level = 9, $supports = null ) { return gzdeflate( $raw, $level ); } /** * Decompression of deflated string. * * Will attempt to decompress using the RFC 1950 standard, and if that fails * then the RFC 1951 standard deflate will be attempted. Finally, the RFC * 1952 standard gzip decode will be attempted. If all fail, then the * original compressed string will be returned. * * @since 2.8.0 * * @static * * @param string $compressed String to decompress. * @param int $length The optional length of the compressed data. * @return string|bool False on failure. */ public static function decompress( $compressed, $length = null ) { if ( empty($compressed) ) return $compressed; if ( false !== ( $decompressed = @gzinflate( $compressed ) ) ) return $decompressed; if ( false !== ( $decompressed = self::compatible_gzinflate( $compressed ) ) ) return $decompressed; if ( false !== ( $decompressed = @gzuncompress( $compressed ) ) ) return $decompressed; if ( function_exists('gzdecode') ) { $decompressed = @gzdecode( $compressed ); if ( false !== $decompressed ) return $decompressed; } return $compressed; } /** * Decompression of deflated string while staying compatible with the majority of servers. * * Certain Servers will return deflated data with headers which PHP's gzinflate() * function cannot handle out of the box. The following function has been created from * various snippets on the gzinflate() PHP documentation. * * Warning: Magic numbers within. Due to the potential different formats that the compressed * data may be returned in, some "magic offsets" are needed to ensure proper decompression * takes place. For a simple progmatic way to determine the magic offset in use, see: * https://core.trac.wordpress.org/ticket/18273 * * @since 2.8.1 * @link https://core.trac.wordpress.org/ticket/18273 * @link https://secure.php.net/manual/en/function.gzinflate.php#70875 * @link https://secure.php.net/manual/en/function.gzinflate.php#77336 * * @static * * @param string $gzData String to decompress. * @return string|bool False on failure. */ public static function compatible_gzinflate($gzData) { // Compressed data might contain a full header, if so strip it for gzinflate(). if ( substr($gzData, 0, 3) == "\x1f\x8b\x08" ) { $i = 10; $flg = ord( substr($gzData, 3, 1) ); if ( $flg > 0 ) { if ( $flg & 4 ) { list($xlen) = unpack('v', substr($gzData, $i, 2) ); $i = $i + 2 + $xlen; } if ( $flg & 8 ) $i = strpos($gzData, "\0", $i) + 1; if ( $flg & 16 ) $i = strpos($gzData, "\0", $i) + 1; if ( $flg & 2 ) $i = $i + 2; } $decompressed = @gzinflate( substr($gzData, $i, -8) ); if ( false !== $decompressed ) return $decompressed; } // Compressed data from java.util.zip.Deflater amongst others. $decompressed = @gzinflate( substr($gzData, 2) ); if ( false !== $decompressed ) return $decompressed; return false; } /** * What encoding types to accept and their priority values. * * @since 2.8.0 * * @static * * @param string $url * @param array $args * @return string Types of encoding to accept. */ public static function accept_encoding( $url, $args ) { $type = array(); $compression_enabled = self::is_available(); if ( ! $args['decompress'] ) // Decompression specifically disabled. $compression_enabled = false; elseif ( $args['stream'] ) // Disable when streaming to file. $compression_enabled = false; elseif ( isset( $args['limit_response_size'] ) ) // If only partial content is being requested, we won't be able to decompress it. $compression_enabled = false; if ( $compression_enabled ) { if ( function_exists( 'gzinflate' ) ) $type[] = 'deflate;q=1.0'; if ( function_exists( 'gzuncompress' ) ) $type[] = 'compress;q=0.5'; if ( function_exists( 'gzdecode' ) ) $type[] = 'gzip;q=0.5'; } /** * Filters the allowed encoding types. * * @since 3.6.0 * * @param array $type Encoding types allowed. Accepts 'gzinflate', * 'gzuncompress', 'gzdecode'. * @param string $url URL of the HTTP request. * @param array $args HTTP request arguments. */ $type = apply_filters( 'wp_http_accept_encoding', $type, $url, $args ); return implode(', ', $type); } /** * What encoding the content used when it was compressed to send in the headers. * * @since 2.8.0 * * @static * * @return string Content-Encoding string to send in the header. */ public static function content_encoding() { return 'deflate'; } /** * Whether the content be decoded based on the headers. * * @since 2.8.0 * * @static * * @param array|string $headers All of the available headers. * @return bool */ public static function should_decode($headers) { if ( is_array( $headers ) ) { if ( array_key_exists('content-encoding', $headers) && ! empty( $headers['content-encoding'] ) ) return true; } elseif ( is_string( $headers ) ) { return ( stripos($headers, 'content-encoding:') !== false ); } return false; } /** * Whether decompression and compression are supported by the PHP version. * * Each function is tested instead of checking for the zlib extension, to * ensure that the functions all exist in the PHP version and aren't * disabled. * * @since 2.8.0 * * @static * * @return bool */ public static function is_available() { return ( function_exists('gzuncompress') || function_exists('gzdeflate') || function_exists('gzinflate') ); } } /** * Widget API: WP_Widget base class * * @package WordPress * @subpackage Widgets * @since 4.4.0 */ /** * Core base class extended to register widgets. * * This class must be extended for each widget, and WP_Widget::widget() must be overridden. * * If adding widget options, WP_Widget::update() and WP_Widget::form() should also be overridden. * * @since 2.8.0 * @since 4.4.0 Moved to its own file from wp-includes/widgets.php */ class WP_Widget { /** * Root ID for all widgets of this type. * * @since 2.8.0 * @access public * @var mixed|string */ public $id_base; /** * Name for this widget type. * * @since 2.8.0 * @access public * @var string */ public $name; /** * Option name for this widget type. * * @since 2.8.0 * @access public * @var string */ public $option_name; /** * Alt option name for this widget type. * * @since 2.8.0 * @access public * @var string */ public $alt_option_name; /** * Option array passed to wp_register_sidebar_widget(). * * @since 2.8.0 * @access public * @var array */ public $widget_options; /** * Option array passed to wp_register_widget_control(). * * @since 2.8.0 * @access public * @var array */ public $control_options; /** * Unique ID number of the current instance. * * @since 2.8.0 * @access public * @var bool|int */ public $number = false; /** * Unique ID string of the current instance (id_base-number). * * @since 2.8.0 * @access public * @var bool|string */ public $id = false; /** * Whether the widget data has been updated. * * Set to true when the data is updated after a POST submit - ensures it does * not happen twice. * * @since 2.8.0 * @access public * @var bool */ public $updated = false; // // Member functions that must be overridden by subclasses. // /** * Echoes the widget content. * * Sub-classes should over-ride this function to generate their widget code. * * @since 2.8.0 * @access public * * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance The settings for the particular instance of the widget. */ public function widget( $args, $instance ) { die('function WP_Widget::widget() must be over-ridden in a sub-class.'); } /** * Updates a particular instance of a widget. * * This function should check that `$new_instance` is set correctly. The newly-calculated * value of `$instance` should be returned. If false is returned, the instance won't be * saved/updated. * * @since 2.8.0 * @access public * * @param array $new_instance New settings for this instance as input by the user via * WP_Widget::form(). * @param array $old_instance Old settings for this instance. * @return array Settings to save or bool false to cancel saving. */ public function update( $new_instance, $old_instance ) { return $new_instance; } /** * Outputs the settings update form. * * @since 2.8.0 * @access public * * @param array $instance Current settings. * @return string Default return is 'noform'. */ public function form( $instance ) { echo '

' . __('There are no options for this widget.') . '

'; return 'noform'; } // Functions you'll need to call. /** * PHP5 constructor. * * @since 2.8.0 * @access public * * @param string $id_base Optional Base ID for the widget, lowercase and unique. If left empty, * a portion of the widget's class name will be used Has to be unique. * @param string $name Name for the widget displayed on the configuration page. * @param array $widget_options Optional. Widget options. See wp_register_sidebar_widget() for information * on accepted arguments. Default empty array. * @param array $control_options Optional. Widget control options. See wp_register_widget_control() for * information on accepted arguments. Default empty array. */ public function __construct( $id_base, $name, $widget_options = array(), $control_options = array() ) { $this->id_base = empty($id_base) ? preg_replace( '/(wp_)?widget_/', '', strtolower(get_class($this)) ) : strtolower($id_base); $this->name = $name; $this->option_name = 'widget_' . $this->id_base; $this->widget_options = wp_parse_args( $widget_options, array( 'classname' => $this->option_name, 'customize_selective_refresh' => false ) ); $this->control_options = wp_parse_args( $control_options, array( 'id_base' => $this->id_base ) ); } /** * PHP4 constructor. * * @since 2.8.0 * @access public * * @see __construct() * * @param string $id_base Optional Base ID for the widget, lowercase and unique. If left empty, * a portion of the widget's class name will be used Has to be unique. * @param string $name Name for the widget displayed on the configuration page. * @param array $widget_options Optional. Widget options. See wp_register_sidebar_widget() for information * on accepted arguments. Default empty array. * @param array $control_options Optional. Widget control options. See wp_register_widget_control() for * information on accepted arguments. Default empty array. */ public function WP_Widget( $id_base, $name, $widget_options = array(), $control_options = array() ) { _deprecated_constructor( 'WP_Widget', '4.3.0', get_class( $this ) ); WP_Widget::__construct( $id_base, $name, $widget_options, $control_options ); } /** * Constructs name attributes for use in form() fields * * This function should be used in form() methods to create name attributes for fields * to be saved by update() * * @since 2.8.0 * @since 4.4.0 Array format field names are now accepted. * @access public * * @param string $field_name Field name * @return string Name attribute for $field_name */ public function get_field_name($field_name) { if ( false === $pos = strpos( $field_name, '[' ) ) { return 'widget-' . $this->id_base . '[' . $this->number . '][' . $field_name . ']'; } else { return 'widget-' . $this->id_base . '[' . $this->number . '][' . substr_replace( $field_name, '][', $pos, strlen( '[' ) ); } } /** * Constructs id attributes for use in WP_Widget::form() fields. * * This function should be used in form() methods to create id attributes * for fields to be saved by WP_Widget::update(). * * @since 2.8.0 * @since 4.4.0 Array format field IDs are now accepted. * @access public * * @param string $field_name Field name. * @return string ID attribute for `$field_name`. */ public function get_field_id( $field_name ) { return 'widget-' . $this->id_base . '-' . $this->number . '-' . trim( str_replace( array( '[]', '[', ']' ), array( '', '-', '' ), $field_name ), '-' ); } /** * Register all widget instances of this widget class. * * @since 2.8.0 * @access public */ public function _register() { $settings = $this->get_settings(); $empty = true; // When $settings is an array-like object, get an intrinsic array for use with array_keys(). if ( $settings instanceof ArrayObject || $settings instanceof ArrayIterator ) { $settings = $settings->getArrayCopy(); } if ( is_array( $settings ) ) { foreach ( array_keys( $settings ) as $number ) { if ( is_numeric( $number ) ) { $this->_set( $number ); $this->_register_one( $number ); $empty = false; } } } if ( $empty ) { // If there are none, we register the widget's existence with a generic template. $this->_set( 1 ); $this->_register_one(); } } /** * Sets the internal order number for the widget instance. * * @since 2.8.0 * @access public * * @param int $number The unique order number of this widget instance compared to other * instances of the same class. */ public function _set($number) { $this->number = $number; $this->id = $this->id_base . '-' . $number; } /** * Retrieves the widget display callback. * * @since 2.8.0 * @access public * * @return callable Display callback. */ public function _get_display_callback() { return array($this, 'display_callback'); } /** * Retrieves the widget update callback. * * @since 2.8.0 * @access public * * @return callable Update callback. */ public function _get_update_callback() { return array($this, 'update_callback'); } /** * Retrieves the form callback. * * @since 2.8.0 * @access public * * @return callable Form callback. */ public function _get_form_callback() { return array($this, 'form_callback'); } /** * Determines whether the current request is inside the Customizer preview. * * If true -- the current request is inside the Customizer preview, then * the object cache gets suspended and widgets should check this to decide * whether they should store anything persistently to the object cache, * to transients, or anywhere else. * * @since 3.9.0 * @access public * * @global WP_Customize_Manager $wp_customize * * @return bool True if within the Customizer preview, false if not. */ public function is_preview() { global $wp_customize; return ( isset( $wp_customize ) && $wp_customize->is_preview() ) ; } /** * Generates the actual widget content (Do NOT override). * * Finds the instance and calls WP_Widget::widget(). * * @since 2.8.0 * @access public * * @param array $args Display arguments. See WP_Widget::widget() for information * on accepted arguments. * @param int|array $widget_args { * Optional. Internal order number of the widget instance, or array of multi-widget arguments. * Default 1. * * @type int $number Number increment used for multiples of the same widget. * } */ public function display_callback( $args, $widget_args = 1 ) { if ( is_numeric( $widget_args ) ) { $widget_args = array( 'number' => $widget_args ); } $widget_args = wp_parse_args( $widget_args, array( 'number' => -1 ) ); $this->_set( $widget_args['number'] ); $instances = $this->get_settings(); if ( array_key_exists( $this->number, $instances ) ) { $instance = $instances[ $this->number ]; /** * Filters the settings for a particular widget instance. * * Returning false will effectively short-circuit display of the widget. * * @since 2.8.0 * * @param array $instance The current widget instance's settings. * @param WP_Widget $this The current widget instance. * @param array $args An array of default widget arguments. */ $instance = apply_filters( 'widget_display_callback', $instance, $this, $args ); if ( false === $instance ) { return; } $was_cache_addition_suspended = wp_suspend_cache_addition(); if ( $this->is_preview() && ! $was_cache_addition_suspended ) { wp_suspend_cache_addition( true ); } $this->widget( $args, $instance ); if ( $this->is_preview() ) { wp_suspend_cache_addition( $was_cache_addition_suspended ); } } } /** * Handles changed settings (Do NOT override). * * @since 2.8.0 * @access public * * @global array $wp_registered_widgets * * @param int $deprecated Not used. */ public function update_callback( $deprecated = 1 ) { global $wp_registered_widgets; $all_instances = $this->get_settings(); // We need to update the data if ( $this->updated ) return; if ( isset($_POST['delete_widget']) && $_POST['delete_widget'] ) { // Delete the settings for this instance of the widget if ( isset($_POST['the-widget-id']) ) $del_id = $_POST['the-widget-id']; else return; if ( isset($wp_registered_widgets[$del_id]['params'][0]['number']) ) { $number = $wp_registered_widgets[$del_id]['params'][0]['number']; if ( $this->id_base . '-' . $number == $del_id ) unset($all_instances[$number]); } } else { if ( isset($_POST['widget-' . $this->id_base]) && is_array($_POST['widget-' . $this->id_base]) ) { $settings = $_POST['widget-' . $this->id_base]; } elseif ( isset($_POST['id_base']) && $_POST['id_base'] == $this->id_base ) { $num = $_POST['multi_number'] ? (int) $_POST['multi_number'] : (int) $_POST['widget_number']; $settings = array( $num => array() ); } else { return; } foreach ( $settings as $number => $new_instance ) { $new_instance = stripslashes_deep($new_instance); $this->_set($number); $old_instance = isset($all_instances[$number]) ? $all_instances[$number] : array(); $was_cache_addition_suspended = wp_suspend_cache_addition(); if ( $this->is_preview() && ! $was_cache_addition_suspended ) { wp_suspend_cache_addition( true ); } $instance = $this->update( $new_instance, $old_instance ); if ( $this->is_preview() ) { wp_suspend_cache_addition( $was_cache_addition_suspended ); } /** * Filters a widget's settings before saving. * * Returning false will effectively short-circuit the widget's ability * to update settings. * * @since 2.8.0 * * @param array $instance The current widget instance's settings. * @param array $new_instance Array of new widget settings. * @param array $old_instance Array of old widget settings. * @param WP_Widget $this The current widget instance. */ $instance = apply_filters( 'widget_update_callback', $instance, $new_instance, $old_instance, $this ); if ( false !== $instance ) { $all_instances[$number] = $instance; } break; // run only once } } $this->save_settings($all_instances); $this->updated = true; } /** * Generates the widget control form (Do NOT override). * * @since 2.8.0 * @access public * * @param int|array $widget_args { * Optional. Internal order number of the widget instance, or array of multi-widget arguments. * Default 1. * * @type int $number Number increment used for multiples of the same widget. * } * @return string|null */ public function form_callback( $widget_args = 1 ) { if ( is_numeric($widget_args) ) $widget_args = array( 'number' => $widget_args ); $widget_args = wp_parse_args( $widget_args, array( 'number' => -1 ) ); $all_instances = $this->get_settings(); if ( -1 == $widget_args['number'] ) { // We echo out a form where 'number' can be set later $this->_set('__i__'); $instance = array(); } else { $this->_set($widget_args['number']); $instance = $all_instances[ $widget_args['number'] ]; } /** * Filters the widget instance's settings before displaying the control form. * * Returning false effectively short-circuits display of the control form. * * @since 2.8.0 * * @param array $instance The current widget instance's settings. * @param WP_Widget $this The current widget instance. */ $instance = apply_filters( 'widget_form_callback', $instance, $this ); $return = null; if ( false !== $instance ) { $return = $this->form($instance); /** * Fires at the end of the widget control form. * * Use this hook to add extra fields to the widget form. The hook * is only fired if the value passed to the 'widget_form_callback' * hook is not false. * * Note: If the widget has no form, the text echoed from the default * form method can be hidden using CSS. * * @since 2.8.0 * * @param WP_Widget $this The widget instance, passed by reference. * @param null $return Return null if new fields are added. * @param array $instance An array of the widget's settings. */ do_action_ref_array( 'in_widget_form', array( &$this, &$return, $instance ) ); } return $return; } /** * Registers an instance of the widget class. * * @since 2.8.0 * @access public * * @param integer $number Optional. The unique order number of this widget instance * compared to other instances of the same class. Default -1. */ public function _register_one( $number = -1 ) { wp_register_sidebar_widget( $this->id, $this->name, $this->_get_display_callback(), $this->widget_options, array( 'number' => $number ) ); _register_widget_update_callback( $this->id_base, $this->_get_update_callback(), $this->control_options, array( 'number' => -1 ) ); _register_widget_form_callback( $this->id, $this->name, $this->_get_form_callback(), $this->control_options, array( 'number' => $number ) ); } /** * Saves the settings for all instances of the widget class. * * @since 2.8.0 * @access public * * @param array $settings Multi-dimensional array of widget instance settings. */ public function save_settings( $settings ) { $settings['_multiwidget'] = 1; update_option( $this->option_name, $settings ); } /** * Retrieves the settings for all instances of the widget class. * * @since 2.8.0 * @access public * * @return array Multi-dimensional array of widget instance settings. */ public function get_settings() { $settings = get_option( $this->option_name ); if ( false === $settings ) { if ( isset( $this->alt_option_name ) ) { $settings = get_option( $this->alt_option_name ); } else { // Save an option so it can be autoloaded next time. $this->save_settings( array() ); } } if ( ! is_array( $settings ) && ! ( $settings instanceof ArrayObject || $settings instanceof ArrayIterator ) ) { $settings = array(); } if ( ! empty( $settings ) && ! isset( $settings['_multiwidget'] ) ) { // Old format, convert if single widget. $settings = wp_convert_widget_settings( $this->id_base, $this->option_name, $settings ); } unset( $settings['_multiwidget'], $settings['__i__'] ); return $settings; } } /** * REST API: WP_REST_Response class * * @package WordPress * @subpackage REST_API * @since 4.4.0 */ /** * Core class used to implement a REST response object. * * @since 4.4.0 * * @see WP_HTTP_Response */ class WP_REST_Response extends WP_HTTP_Response { /** * Links related to the response. * * @since 4.4.0 * @access protected * @var array */ protected $links = array(); /** * The route that was to create the response. * * @since 4.4.0 * @access protected * @var string */ protected $matched_route = ''; /** * The handler that was used to create the response. * * @since 4.4.0 * @access protected * @var null|array */ protected $matched_handler = null; /** * Adds a link to the response. * * @internal The $rel parameter is first, as this looks nicer when sending multiple. * * @since 4.4.0 * @access public * * @link https://tools.ietf.org/html/rfc5988 * @link https://www.iana.org/assignments/link-relations/link-relations.xml * * @param string $rel Link relation. Either an IANA registered type, * or an absolute URL. * @param string $href Target URI for the link. * @param array $attributes Optional. Link parameters to send along with the URL. Default empty array. */ public function add_link( $rel, $href, $attributes = array() ) { if ( empty( $this->links[ $rel ] ) ) { $this->links[ $rel ] = array(); } if ( isset( $attributes['href'] ) ) { // Remove the href attribute, as it's used for the main URL. unset( $attributes['href'] ); } $this->links[ $rel ][] = array( 'href' => $href, 'attributes' => $attributes, ); } /** * Removes a link from the response. * * @since 4.4.0 * @access public * * @param string $rel Link relation. Either an IANA registered type, or an absolute URL. * @param string $href Optional. Only remove links for the relation matching the given href. * Default null. */ public function remove_link( $rel, $href = null ) { if ( ! isset( $this->links[ $rel ] ) ) { return; } if ( $href ) { $this->links[ $rel ] = wp_list_filter( $this->links[ $rel ], array( 'href' => $href ), 'NOT' ); } else { $this->links[ $rel ] = array(); } if ( ! $this->links[ $rel ] ) { unset( $this->links[ $rel ] ); } } /** * Adds multiple links to the response. * * Link data should be an associative array with link relation as the key. * The value can either be an associative array of link attributes * (including `href` with the URL for the response), or a list of these * associative arrays. * * @since 4.4.0 * @access public * * @param array $links Map of link relation to list of links. */ public function add_links( $links ) { foreach ( $links as $rel => $set ) { // If it's a single link, wrap with an array for consistent handling. if ( isset( $set['href'] ) ) { $set = array( $set ); } foreach ( $set as $attributes ) { $this->add_link( $rel, $attributes['href'], $attributes ); } } } /** * Retrieves links for the response. * * @since 4.4.0 * @access public * * @return array List of links. */ public function get_links() { return $this->links; } /** * Sets a single link header. * * @internal The $rel parameter is first, as this looks nicer when sending multiple. * * @since 4.4.0 * @access public * * @link https://tools.ietf.org/html/rfc5988 * @link https://www.iana.org/assignments/link-relations/link-relations.xml * * @param string $rel Link relation. Either an IANA registered type, or an absolute URL. * @param string $link Target IRI for the link. * @param array $other Optional. Other parameters to send, as an assocative array. * Default empty array. */ public function link_header( $rel, $link, $other = array() ) { $header = '<' . $link . '>; rel="' . $rel . '"'; foreach ( $other as $key => $value ) { if ( 'title' === $key ) { $value = '"' . $value . '"'; } $header .= '; ' . $key . '=' . $value; } $this->header( 'Link', $header, false ); } /** * Retrieves the route that was used. * * @since 4.4.0 * @access public * * @return string The matched route. */ public function get_matched_route() { return $this->matched_route; } /** * Sets the route (regex for path) that caused the response. * * @since 4.4.0 * @access public * * @param string $route Route name. */ public function set_matched_route( $route ) { $this->matched_route = $route; } /** * Retrieves the handler that was used to generate the response. * * @since 4.4.0 * @access public * * @return null|array The handler that was used to create the response. */ public function get_matched_handler() { return $this->matched_handler; } /** * Retrieves the handler that was responsible for generating the response. * * @since 4.4.0 * @access public * * @param array $handler The matched handler. */ public function set_matched_handler( $handler ) { $this->matched_handler = $handler; } /** * Checks if the response is an error, i.e. >= 400 response code. * * @since 4.4.0 * @access public * * @return bool Whether the response is an error. */ public function is_error() { return $this->get_status() >= 400; } /** * Retrieves a WP_Error object from the response. * * @since 4.4.0 * @access public * * @return WP_Error|null WP_Error or null on not an errored response. */ public function as_error() { if ( ! $this->is_error() ) { return null; } $error = new WP_Error; if ( is_array( $this->get_data() ) ) { $data = $this->get_data(); $error->add( $data['code'], $data['message'], $data['data'] ); if ( ! empty( $data['additional_errors'] ) ) { foreach( $data['additional_errors'] as $err ) { $error->add( $err['code'], $err['message'], $err['data'] ); } } } else { $error->add( $this->get_status(), '', array( 'status' => $this->get_status() ) ); } return $error; } /** * Retrieves the CURIEs (compact URIs) used for relations. * * @since 4.5.0 * @access public * * @return array Compact URIs. */ public function get_curies() { $curies = array( array( 'name' => 'wp', 'href' => 'https://api.w.org/{rel}', 'templated' => true, ), ); /** * Filters extra CURIEs available on API responses. * * CURIEs allow a shortened version of URI relations. This allows a more * usable form for custom relations than using the full URI. These work * similarly to how XML namespaces work. * * Registered CURIES need to specify a name and URI template. This will * automatically transform URI relations into their shortened version. * The shortened relation follows the format `{name}:{rel}`. `{rel}` in * the URI template will be replaced with the `{rel}` part of the * shortened relation. * * For example, a CURIE with name `example` and URI template * `http://w.org/{rel}` would transform a `http://w.org/term` relation * into `example:term`. * * Well-behaved clients should expand and normalise these back to their * full URI relation, however some naive clients may not resolve these * correctly, so adding new CURIEs may break backward compatibility. * * @since 4.5.0 * * @param array $additional Additional CURIEs to register with the API. */ $additional = apply_filters( 'rest_response_link_curies', array() ); return array_merge( $curies, $additional ); } } /** * REST API: WP_REST_Request class * * @package WordPress * @subpackage REST_API * @since 4.4.0 */ /** * Core class used to implement a REST request object. * * Contains data from the request, to be passed to the callback. * * Note: This implements ArrayAccess, and acts as an array of parameters when * used in that manner. It does not use ArrayObject (as we cannot rely on SPL), * so be aware it may have non-array behaviour in some cases. * * Note: When using features provided by ArrayAccess, be aware that WordPress deliberately * does not distinguish between arguments of the same name for different request methods. * For instance, in a request with `GET id=1` and `POST id=2`, `$request['id']` will equal * 2 (`POST`) not 1 (`GET`). For more precision between request methods, use * WP_REST_Request::get_body_params(), WP_REST_Request::get_url_params(), etc. * * @since 4.4.0 * * @see ArrayAccess */ class WP_REST_Request implements ArrayAccess { /** * HTTP method. * * @since 4.4.0 * @access protected * @var string */ protected $method = ''; /** * Parameters passed to the request. * * These typically come from the `$_GET`, `$_POST` and `$_FILES` * superglobals when being created from the global scope. * * @since 4.4.0 * @access protected * @var array Contains GET, POST and FILES keys mapping to arrays of data. */ protected $params; /** * HTTP headers for the request. * * @since 4.4.0 * @access protected * @var array Map of key to value. Key is always lowercase, as per HTTP specification. */ protected $headers = array(); /** * Body data. * * @since 4.4.0 * @access protected * @var string Binary data from the request. */ protected $body = null; /** * Route matched for the request. * * @since 4.4.0 * @access protected * @var string */ protected $route; /** * Attributes (options) for the route that was matched. * * This is the options array used when the route was registered, typically * containing the callback as well as the valid methods for the route. * * @since 4.4.0 * @access protected * @var array Attributes for the request. */ protected $attributes = array(); /** * Used to determine if the JSON data has been parsed yet. * * Allows lazy-parsing of JSON data where possible. * * @since 4.4.0 * @access protected * @var bool */ protected $parsed_json = false; /** * Used to determine if the body data has been parsed yet. * * @since 4.4.0 * @access protected * @var bool */ protected $parsed_body = false; /** * Constructor. * * @since 4.4.0 * @access public * * @param string $method Optional. Request method. Default empty. * @param string $route Optional. Request route. Default empty. * @param array $attributes Optional. Request attributes. Default empty array. */ public function __construct( $method = '', $route = '', $attributes = array() ) { $this->params = array( 'URL' => array(), 'GET' => array(), 'POST' => array(), 'FILES' => array(), // See parse_json_params. 'JSON' => null, 'defaults' => array(), ); $this->set_method( $method ); $this->set_route( $route ); $this->set_attributes( $attributes ); } /** * Retrieves the HTTP method for the request. * * @since 4.4.0 * @access public * * @return string HTTP method. */ public function get_method() { return $this->method; } /** * Sets HTTP method for the request. * * @since 4.4.0 * @access public * * @param string $method HTTP method. */ public function set_method( $method ) { $this->method = strtoupper( $method ); } /** * Retrieves all headers from the request. * * @since 4.4.0 * @access public * * @return array Map of key to value. Key is always lowercase, as per HTTP specification. */ public function get_headers() { return $this->headers; } /** * Canonicalizes the header name. * * Ensures that header names are always treated the same regardless of * source. Header names are always case insensitive. * * Note that we treat `-` (dashes) and `_` (underscores) as the same * character, as per header parsing rules in both Apache and nginx. * * @link http://stackoverflow.com/q/18185366 * @link http://wiki.nginx.org/Pitfalls#Missing_.28disappearing.29_HTTP_headers * @link https://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers * * @since 4.4.0 * @access public * @static * * @param string $key Header name. * @return string Canonicalized name. */ public static function canonicalize_header_name( $key ) { $key = strtolower( $key ); $key = str_replace( '-', '_', $key ); return $key; } /** * Retrieves the given header from the request. * * If the header has multiple values, they will be concatenated with a comma * as per the HTTP specification. Be aware that some non-compliant headers * (notably cookie headers) cannot be joined this way. * * @since 4.4.0 * @access public * * @param string $key Header name, will be canonicalized to lowercase. * @return string|null String value if set, null otherwise. */ public function get_header( $key ) { $key = $this->canonicalize_header_name( $key ); if ( ! isset( $this->headers[ $key ] ) ) { return null; } return implode( ',', $this->headers[ $key ] ); } /** * Retrieves header values from the request. * * @since 4.4.0 * @access public * * @param string $key Header name, will be canonicalized to lowercase. * @return array|null List of string values if set, null otherwise. */ public function get_header_as_array( $key ) { $key = $this->canonicalize_header_name( $key ); if ( ! isset( $this->headers[ $key ] ) ) { return null; } return $this->headers[ $key ]; } /** * Sets the header on request. * * @since 4.4.0 * @access public * * @param string $key Header name. * @param string $value Header value, or list of values. */ public function set_header( $key, $value ) { $key = $this->canonicalize_header_name( $key ); $value = (array) $value; $this->headers[ $key ] = $value; } /** * Appends a header value for the given header. * * @since 4.4.0 * @access public * * @param string $key Header name. * @param string $value Header value, or list of values. */ public function add_header( $key, $value ) { $key = $this->canonicalize_header_name( $key ); $value = (array) $value; if ( ! isset( $this->headers[ $key ] ) ) { $this->headers[ $key ] = array(); } $this->headers[ $key ] = array_merge( $this->headers[ $key ], $value ); } /** * Removes all values for a header. * * @since 4.4.0 * @access public * * @param string $key Header name. */ public function remove_header( $key ) { unset( $this->headers[ $key ] ); } /** * Sets headers on the request. * * @since 4.4.0 * @access public * * @param array $headers Map of header name to value. * @param bool $override If true, replace the request's headers. Otherwise, merge with existing. */ public function set_headers( $headers, $override = true ) { if ( true === $override ) { $this->headers = array(); } foreach ( $headers as $key => $value ) { $this->set_header( $key, $value ); } } /** * Retrieves the content-type of the request. * * @since 4.4.0 * @access public * * @return array Map containing 'value' and 'parameters' keys. */ public function get_content_type() { $value = $this->get_header( 'content-type' ); if ( empty( $value ) ) { return null; } $parameters = ''; if ( strpos( $value, ';' ) ) { list( $value, $parameters ) = explode( ';', $value, 2 ); } $value = strtolower( $value ); if ( strpos( $value, '/' ) === false ) { return null; } // Parse type and subtype out. list( $type, $subtype ) = explode( '/', $value, 2 ); $data = compact( 'value', 'type', 'subtype', 'parameters' ); $data = array_map( 'trim', $data ); return $data; } /** * Retrieves the parameter priority order. * * Used when checking parameters in get_param(). * * @since 4.4.0 * @access protected * * @return array List of types to check, in order of priority. */ protected function get_parameter_order() { $order = array(); $order[] = 'JSON'; $this->parse_json_params(); // Ensure we parse the body data. $body = $this->get_body(); if ( 'POST' !== $this->method && ! empty( $body ) ) { $this->parse_body_params(); } $accepts_body_data = array( 'POST', 'PUT', 'PATCH', 'DELETE' ); if ( in_array( $this->method, $accepts_body_data ) ) { $order[] = 'POST'; } $order[] = 'GET'; $order[] = 'URL'; $order[] = 'defaults'; /** * Filters the parameter order. * * The order affects which parameters are checked when using get_param() and family. * This acts similarly to PHP's `request_order` setting. * * @since 4.4.0 * * @param array $order { * An array of types to check, in order of priority. * * @param string $type The type to check. * } * @param WP_REST_Request $this The request object. */ return apply_filters( 'rest_request_parameter_order', $order, $this ); } /** * Retrieves a parameter from the request. * * @since 4.4.0 * @access public * * @param string $key Parameter name. * @return mixed|null Value if set, null otherwise. */ public function get_param( $key ) { $order = $this->get_parameter_order(); foreach ( $order as $type ) { // Determine if we have the parameter for this type. if ( isset( $this->params[ $type ][ $key ] ) ) { return $this->params[ $type ][ $key ]; } } return null; } /** * Sets a parameter on the request. * * @since 4.4.0 * @access public * * @param string $key Parameter name. * @param mixed $value Parameter value. */ public function set_param( $key, $value ) { switch ( $this->method ) { case 'POST': $this->params['POST'][ $key ] = $value; break; default: $this->params['GET'][ $key ] = $value; break; } } /** * Retrieves merged parameters from the request. * * The equivalent of get_param(), but returns all parameters for the request. * Handles merging all the available values into a single array. * * @since 4.4.0 * @access public * * @return array Map of key to value. */ public function get_params() { $order = $this->get_parameter_order(); $order = array_reverse( $order, true ); $params = array(); foreach ( $order as $type ) { // array_merge / the "+" operator will mess up // numeric keys, so instead do a manual foreach. foreach ( (array) $this->params[ $type ] as $key => $value ) { $params[ $key ] = $value; } } return $params; } /** * Retrieves parameters from the route itself. * * These are parsed from the URL using the regex. * * @since 4.4.0 * @access public * * @return array Parameter map of key to value. */ public function get_url_params() { return $this->params['URL']; } /** * Sets parameters from the route. * * Typically, this is set after parsing the URL. * * @since 4.4.0 * @access public * * @param array $params Parameter map of key to value. */ public function set_url_params( $params ) { $this->params['URL'] = $params; } /** * Retrieves parameters from the query string. * * These are the parameters you'd typically find in `$_GET`. * * @since 4.4.0 * @access public * * @return array Parameter map of key to value */ public function get_query_params() { return $this->params['GET']; } /** * Sets parameters from the query string. * * Typically, this is set from `$_GET`. * * @since 4.4.0 * @access public * * @param array $params Parameter map of key to value. */ public function set_query_params( $params ) { $this->params['GET'] = $params; } /** * Retrieves parameters from the body. * * These are the parameters you'd typically find in `$_POST`. * * @since 4.4.0 * @access public * * @return array Parameter map of key to value. */ public function get_body_params() { return $this->params['POST']; } /** * Sets parameters from the body. * * Typically, this is set from `$_POST`. * * @since 4.4.0 * @access public * * @param array $params Parameter map of key to value. */ public function set_body_params( $params ) { $this->params['POST'] = $params; } /** * Retrieves multipart file parameters from the body. * * These are the parameters you'd typically find in `$_FILES`. * * @since 4.4.0 * @access public * * @return array Parameter map of key to value */ public function get_file_params() { return $this->params['FILES']; } /** * Sets multipart file parameters from the body. * * Typically, this is set from `$_FILES`. * * @since 4.4.0 * @access public * * @param array $params Parameter map of key to value. */ public function set_file_params( $params ) { $this->params['FILES'] = $params; } /** * Retrieves the default parameters. * * These are the parameters set in the route registration. * * @since 4.4.0 * @access public * * @return array Parameter map of key to value */ public function get_default_params() { return $this->params['defaults']; } /** * Sets default parameters. * * These are the parameters set in the route registration. * * @since 4.4.0 * @access public * * @param array $params Parameter map of key to value. */ public function set_default_params( $params ) { $this->params['defaults'] = $params; } /** * Retrieves the request body content. * * @since 4.4.0 * @access public * * @return string Binary data from the request body. */ public function get_body() { return $this->body; } /** * Sets body content. * * @since 4.4.0 * @access public * * @param string $data Binary data from the request body. */ public function set_body( $data ) { $this->body = $data; // Enable lazy parsing. $this->parsed_json = false; $this->parsed_body = false; $this->params['JSON'] = null; } /** * Retrieves the parameters from a JSON-formatted body. * * @since 4.4.0 * @access public * * @return array Parameter map of key to value. */ public function get_json_params() { // Ensure the parameters have been parsed out. $this->parse_json_params(); return $this->params['JSON']; } /** * Parses the JSON parameters. * * Avoids parsing the JSON data until we need to access it. * * @since 4.4.0 * @since 4.7.0 Returns error instance if value cannot be decoded. * @access protected * @return true|WP_Error True if the JSON data was passed or no JSON data was provided, WP_Error if invalid JSON was passed. */ protected function parse_json_params() { if ( $this->parsed_json ) { return true; } $this->parsed_json = true; // Check that we actually got JSON. $content_type = $this->get_content_type(); if ( empty( $content_type ) || 'application/json' !== $content_type['value'] ) { return true; } $body = $this->get_body(); if ( empty( $body ) ) { return true; } $params = json_decode( $body, true ); /* * Check for a parsing error. * * Note that due to WP's JSON compatibility functions, json_last_error * might not be defined: https://core.trac.wordpress.org/ticket/27799 */ if ( null === $params && ( ! function_exists( 'json_last_error' ) || JSON_ERROR_NONE !== json_last_error() ) ) { // Ensure subsequent calls receive error instance. $this->parsed_json = false; $error_data = array( 'status' => WP_Http::BAD_REQUEST, ); if ( function_exists( 'json_last_error' ) ) { $error_data['json_error_code'] = json_last_error(); $error_data['json_error_message'] = json_last_error_msg(); } return new WP_Error( 'rest_invalid_json', __( 'Invalid JSON body passed.' ), $error_data ); } $this->params['JSON'] = $params; return true; } /** * Parses the request body parameters. * * Parses out URL-encoded bodies for request methods that aren't supported * natively by PHP. In PHP 5.x, only POST has these parsed automatically. * * @since 4.4.0 * @access protected */ protected function parse_body_params() { if ( $this->parsed_body ) { return; } $this->parsed_body = true; /* * Check that we got URL-encoded. Treat a missing content-type as * URL-encoded for maximum compatibility. */ $content_type = $this->get_content_type(); if ( ! empty( $content_type ) && 'application/x-www-form-urlencoded' !== $content_type['value'] ) { return; } parse_str( $this->get_body(), $params ); /* * Amazingly, parse_str follows magic quote rules. Sigh. * * NOTE: Do not refactor to use `wp_unslash`. */ if ( get_magic_quotes_gpc() ) { $params = stripslashes_deep( $params ); } /* * Add to the POST parameters stored internally. If a user has already * set these manually (via `set_body_params`), don't override them. */ $this->params['POST'] = array_merge( $params, $this->params['POST'] ); } /** * Retrieves the route that matched the request. * * @since 4.4.0 * @access public * * @return string Route matching regex. */ public function get_route() { return $this->route; } /** * Sets the route that matched the request. * * @since 4.4.0 * @access public * * @param string $route Route matching regex. */ public function set_route( $route ) { $this->route = $route; } /** * Retrieves the attributes for the request. * * These are the options for the route that was matched. * * @since 4.4.0 * @access public * * @return array Attributes for the request. */ public function get_attributes() { return $this->attributes; } /** * Sets the attributes for the request. * * @since 4.4.0 * @access public * * @param array $attributes Attributes for the request. */ public function set_attributes( $attributes ) { $this->attributes = $attributes; } /** * Sanitizes (where possible) the params on the request. * * This is primarily based off the sanitize_callback param on each registered * argument. * * @since 4.4.0 * @access public * * @return true|WP_Error True if parameters were sanitized, WP_Error if an error occurred during sanitization. */ public function sanitize_params() { $attributes = $this->get_attributes(); // No arguments set, skip sanitizing. if ( empty( $attributes['args'] ) ) { return true; } $order = $this->get_parameter_order(); $invalid_params = array(); foreach ( $order as $type ) { if ( empty( $this->params[ $type ] ) ) { continue; } foreach ( $this->params[ $type ] as $key => $value ) { if ( ! isset( $attributes['args'][ $key ] ) ) { continue; } $param_args = $attributes['args'][ $key ]; // If the arg has a type but no sanitize_callback attribute, default to rest_parse_request_arg. if ( ! array_key_exists( 'sanitize_callback', $param_args ) && ! empty( $param_args['type'] ) ) { $param_args['sanitize_callback'] = 'rest_parse_request_arg'; } // If there's still no sanitize_callback, nothing to do here. if ( empty( $param_args['sanitize_callback'] ) ) { continue; } $sanitized_value = call_user_func( $param_args['sanitize_callback'], $value, $this, $key ); if ( is_wp_error( $sanitized_value ) ) { $invalid_params[ $key ] = $sanitized_value->get_error_message(); } else { $this->params[ $type ][ $key ] = $sanitized_value; } } } if ( $invalid_params ) { return new WP_Error( 'rest_invalid_param', sprintf( __( 'Invalid parameter(s): %s' ), implode( ', ', array_keys( $invalid_params ) ) ), array( 'status' => 400, 'params' => $invalid_params ) ); } return true; } /** * Checks whether this request is valid according to its attributes. * * @since 4.4.0 * @access public * * @return bool|WP_Error True if there are no parameters to validate or if all pass validation, * WP_Error if required parameters are missing. */ public function has_valid_params() { // If JSON data was passed, check for errors. $json_error = $this->parse_json_params(); if ( is_wp_error( $json_error ) ) { return $json_error; } $attributes = $this->get_attributes(); $required = array(); // No arguments set, skip validation. if ( empty( $attributes['args'] ) ) { return true; } foreach ( $attributes['args'] as $key => $arg ) { $param = $this->get_param( $key ); if ( isset( $arg['required'] ) && true === $arg['required'] && null === $param ) { $required[] = $key; } } if ( ! empty( $required ) ) { return new WP_Error( 'rest_missing_callback_param', sprintf( __( 'Missing parameter(s): %s' ), implode( ', ', $required ) ), array( 'status' => 400, 'params' => $required ) ); } /* * Check the validation callbacks for each registered arg. * * This is done after required checking as required checking is cheaper. */ $invalid_params = array(); foreach ( $attributes['args'] as $key => $arg ) { $param = $this->get_param( $key ); if ( null !== $param && ! empty( $arg['validate_callback'] ) ) { $valid_check = call_user_func( $arg['validate_callback'], $param, $this, $key ); if ( false === $valid_check ) { $invalid_params[ $key ] = __( 'Invalid parameter.' ); } if ( is_wp_error( $valid_check ) ) { $invalid_params[ $key ] = $valid_check->get_error_message(); } } } if ( $invalid_params ) { return new WP_Error( 'rest_invalid_param', sprintf( __( 'Invalid parameter(s): %s' ), implode( ', ', array_keys( $invalid_params ) ) ), array( 'status' => 400, 'params' => $invalid_params ) ); } return true; } /** * Checks if a parameter is set. * * @since 4.4.0 * @access public * * @param string $offset Parameter name. * @return bool Whether the parameter is set. */ public function offsetExists( $offset ) { $order = $this->get_parameter_order(); foreach ( $order as $type ) { if ( isset( $this->params[ $type ][ $offset ] ) ) { return true; } } return false; } /** * Retrieves a parameter from the request. * * @since 4.4.0 * @access public * * @param string $offset Parameter name. * @return mixed|null Value if set, null otherwise. */ public function offsetGet( $offset ) { return $this->get_param( $offset ); } /** * Sets a parameter on the request. * * @since 4.4.0 * @access public * * @param string $offset Parameter name. * @param mixed $value Parameter value. */ public function offsetSet( $offset, $value ) { $this->set_param( $offset, $value ); } /** * Removes a parameter from the request. * * @since 4.4.0 * @access public * * @param string $offset Parameter name. */ public function offsetUnset( $offset ) { $order = $this->get_parameter_order(); // Remove the offset from every group. foreach ( $order as $type ) { unset( $this->params[ $type ][ $offset ] ); } } /** * Retrieves a WP_REST_Request object from a full URL. * * @static * @since 4.5.0 * @access public * * @param string $url URL with protocol, domain, path and query args. * @return WP_REST_Request|false WP_REST_Request object on success, false on failure. */ public static function from_url( $url ) { $bits = parse_url( $url ); $query_params = array(); if ( ! empty( $bits['query'] ) ) { wp_parse_str( $bits['query'], $query_params ); } $api_root = rest_url(); if ( get_option( 'permalink_structure' ) && 0 === strpos( $url, $api_root ) ) { // Pretty permalinks on, and URL is under the API root. $api_url_part = substr( $url, strlen( untrailingslashit( $api_root ) ) ); $route = parse_url( $api_url_part, PHP_URL_PATH ); } elseif ( ! empty( $query_params['rest_route'] ) ) { // ?rest_route=... set directly $route = $query_params['rest_route']; unset( $query_params['rest_route'] ); } $request = false; if ( ! empty( $route ) ) { $request = new WP_REST_Request( 'GET', $route ); $request->set_query_params( $query_params ); } /** * Filters the request generated from a URL. * * @since 4.5.0 * * @param WP_REST_Request|false $request Generated request object, or false if URL * could not be parsed. * @param string $url URL the request was generated from. */ return apply_filters( 'rest_request_from_url', $request, $url ); } } /** * REST API: WP_REST_Meta_Fields class * * @package WordPress * @subpackage REST_API * @since 4.7.0 */ /** * Core class to manage meta values for an object via the REST API. * * @since 4.7.0 */ abstract class WP_REST_Meta_Fields { /** * Retrieves the object meta type. * * @since 4.7.0 * @access protected * * @return string One of 'post', 'comment', 'term', 'user', or anything * else supported by `_get_meta_table()`. */ abstract protected function get_meta_type(); /** * Retrieves the object type for register_rest_field(). * * @since 4.7.0 * @access protected * * @return string The REST field type, such as post type name, taxonomy name, 'comment', or `user`. */ abstract protected function get_rest_field_type(); /** * Registers the meta field. * * @since 4.7.0 * @access public * * @see register_rest_field() */ public function register_field() { register_rest_field( $this->get_rest_field_type(), 'meta', array( 'get_callback' => array( $this, 'get_value' ), 'update_callback' => array( $this, 'update_value' ), 'schema' => $this->get_field_schema(), )); } /** * Retrieves the meta field value. * * @since 4.7.0 * @access public * * @param int $object_id Object ID to fetch meta for. * @param WP_REST_Request $request Full details about the request. * @return WP_Error|object Object containing the meta values by name, otherwise WP_Error object. */ public function get_value( $object_id, $request ) { $fields = $this->get_registered_fields(); $response = array(); foreach ( $fields as $meta_key => $args ) { $name = $args['name']; $all_values = get_metadata( $this->get_meta_type(), $object_id, $meta_key, false ); if ( $args['single'] ) { if ( empty( $all_values ) ) { $value = $args['schema']['default']; } else { $value = $all_values[0]; } $value = $this->prepare_value_for_response( $value, $request, $args ); } else { $value = array(); foreach ( $all_values as $row ) { $value[] = $this->prepare_value_for_response( $row, $request, $args ); } } $response[ $name ] = $value; } return $response; } /** * Prepares a meta value for a response. * * This is required because some native types cannot be stored correctly * in the database, such as booleans. We need to cast back to the relevant * type before passing back to JSON. * * @since 4.7.0 * @access protected * * @param mixed $value Meta value to prepare. * @param WP_REST_Request $request Current request object. * @param array $args Options for the field. * @return mixed Prepared value. */ protected function prepare_value_for_response( $value, $request, $args ) { if ( ! empty( $args['prepare_callback'] ) ) { $value = call_user_func( $args['prepare_callback'], $value, $request, $args ); } return $value; } /** * Updates meta values. * * @since 4.7.0 * @access public * * @param array $meta Array of meta parsed from the request. * @param int $object_id Object ID to fetch meta for. * @return WP_Error|null WP_Error if one occurs, null on success. */ public function update_value( $meta, $object_id ) { $fields = $this->get_registered_fields(); foreach ( $fields as $meta_key => $args ) { $name = $args['name']; if ( ! array_key_exists( $name, $meta ) ) { continue; } /* * A null value means reset the field, which is essentially deleting it * from the database and then relying on the default value. */ if ( is_null( $meta[ $name ] ) ) { $result = $this->delete_meta_value( $object_id, $meta_key, $name ); if ( is_wp_error( $result ) ) { return $result; } continue; } $is_valid = rest_validate_value_from_schema( $meta[ $name ], $args['schema'], 'meta.' . $name ); if ( is_wp_error( $is_valid ) ) { $is_valid->add_data( array( 'status' => 400 ) ); return $is_valid; } $value = rest_sanitize_value_from_schema( $meta[ $name ], $args['schema'] ); if ( $args['single'] ) { $result = $this->update_meta_value( $object_id, $meta_key, $name, $value ); } else { $result = $this->update_multi_meta_value( $object_id, $meta_key, $name, $value ); } if ( is_wp_error( $result ) ) { return $result; } } return null; } /** * Deletes a meta value for an object. * * @since 4.7.0 * @access protected * * @param int $object_id Object ID the field belongs to. * @param string $meta_key Key for the field. * @param string $name Name for the field that is exposed in the REST API. * @return bool|WP_Error True if meta field is deleted, WP_Error otherwise. */ protected function delete_meta_value( $object_id, $meta_key, $name ) { $meta_type = $this->get_meta_type(); if ( ! current_user_can( "delete_{$meta_type}_meta", $object_id, $meta_key ) ) { return new WP_Error( 'rest_cannot_delete', /* translators: %s: custom field key */ sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ), array( 'key' => $name, 'status' => rest_authorization_required_code() ) ); } if ( ! delete_metadata( $meta_type, $object_id, wp_slash( $meta_key ) ) ) { return new WP_Error( 'rest_meta_database_error', __( 'Could not delete meta value from database.' ), array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR ) ); } return true; } /** * Updates multiple meta values for an object. * * Alters the list of values in the database to match the list of provided values. * * @since 4.7.0 * @access protected * * @param int $object_id Object ID to update. * @param string $meta_key Key for the custom field. * @param string $name Name for the field that is exposed in the REST API. * @param array $values List of values to update to. * @return bool|WP_Error True if meta fields are updated, WP_Error otherwise. */ protected function update_multi_meta_value( $object_id, $meta_key, $name, $values ) { $meta_type = $this->get_meta_type(); if ( ! current_user_can( "edit_{$meta_type}_meta", $object_id, $meta_key ) ) { return new WP_Error( 'rest_cannot_update', /* translators: %s: custom field key */ sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ), array( 'key' => $name, 'status' => rest_authorization_required_code() ) ); } $current = get_metadata( $meta_type, $object_id, $meta_key, false ); $to_remove = $current; $to_add = $values; foreach ( $to_add as $add_key => $value ) { $remove_keys = array_keys( $to_remove, $value, true ); if ( empty( $remove_keys ) ) { continue; } if ( count( $remove_keys ) > 1 ) { // To remove, we need to remove first, then add, so don't touch. continue; } $remove_key = $remove_keys[0]; unset( $to_remove[ $remove_key ] ); unset( $to_add[ $add_key ] ); } // `delete_metadata` removes _all_ instances of the value, so only call once. $to_remove = array_unique( $to_remove ); foreach ( $to_remove as $value ) { if ( ! delete_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) { return new WP_Error( 'rest_meta_database_error', __( 'Could not update meta value in database.' ), array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR ) ); } } foreach ( $to_add as $value ) { if ( ! add_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) { return new WP_Error( 'rest_meta_database_error', __( 'Could not update meta value in database.' ), array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR ) ); } } return true; } /** * Updates a meta value for an object. * * @since 4.7.0 * @access protected * * @param int $object_id Object ID to update. * @param string $meta_key Key for the custom field. * @param string $name Name for the field that is exposed in the REST API. * @param mixed $value Updated value. * @return bool|WP_Error True if the meta field was updated, WP_Error otherwise. */ protected function update_meta_value( $object_id, $meta_key, $name, $value ) { $meta_type = $this->get_meta_type(); if ( ! current_user_can( "edit_{$meta_type}_meta", $object_id, $meta_key ) ) { return new WP_Error( 'rest_cannot_update', /* translators: %s: custom field key */ sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ), array( 'key' => $name, 'status' => rest_authorization_required_code() ) ); } $meta_key = wp_slash( $meta_key ); $meta_value = wp_slash( $value ); // Do the exact same check for a duplicate value as in update_metadata() to avoid update_metadata() returning false. $old_value = get_metadata( $meta_type, $object_id, $meta_key ); if ( 1 === count( $old_value ) ) { if ( $old_value[0] === $meta_value ) { return true; } } if ( ! update_metadata( $meta_type, $object_id, $meta_key, $meta_value ) ) { return new WP_Error( 'rest_meta_database_error', __( 'Could not update meta value in database.' ), array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR ) ); } return true; } /** * Retrieves all the registered meta fields. * * @since 4.7.0 * @access protected * * @return array Registered fields. */ protected function get_registered_fields() { $registered = array(); foreach ( get_registered_meta_keys( $this->get_meta_type() ) as $name => $args ) { if ( empty( $args['show_in_rest'] ) ) { continue; } $rest_args = array(); if ( is_array( $args['show_in_rest'] ) ) { $rest_args = $args['show_in_rest']; } $default_args = array( 'name' => $name, 'single' => $args['single'], 'type' => ! empty( $args['type'] ) ? $args['type'] : null, 'schema' => array(), 'prepare_callback' => array( $this, 'prepare_value' ), ); $default_schema = array( 'type' => $default_args['type'], 'description' => empty( $args['description'] ) ? '' : $args['description'], 'default' => isset( $args['default'] ) ? $args['default'] : null, ); $rest_args = array_merge( $default_args, $rest_args ); $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] ); $type = ! empty( $rest_args['type'] ) ? $rest_args['type'] : null; $type = ! empty( $rest_args['schema']['type'] ) ? $rest_args['schema']['type'] : $type; if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number' ) ) ) { continue; } if ( empty( $rest_args['single'] ) ) { $rest_args['schema']['items'] = array( 'type' => $rest_args['type'], ); $rest_args['schema']['type'] = 'array'; } $registered[ $name ] = $rest_args; } return $registered; } /** * Retrieves the object's meta schema, conforming to JSON Schema. * * @since 4.7.0 * @access protected * * @return array Field schema data. */ public function get_field_schema() { $fields = $this->get_registered_fields(); $schema = array( 'description' => __( 'Meta fields.' ), 'type' => 'object', 'context' => array( 'view', 'edit' ), 'properties' => array(), 'arg_options' => array( 'sanitize_callback' => null, 'validate_callback' => array( $this, 'check_meta_is_array' ), ), ); foreach ( $fields as $args ) { $schema['properties'][ $args['name'] ] = $args['schema']; } return $schema; } /** * Prepares a meta value for output. * * Default preparation for meta fields. Override by passing the * `prepare_callback` in your `show_in_rest` options. * * @since 4.7.0 * @access public * * @param mixed $value Meta value from the database. * @param WP_REST_Request $request Request object. * @param array $args REST-specific options for the meta key. * @return mixed Value prepared for output. If a non-JsonSerializable object, null. */ public static function prepare_value( $value, $request, $args ) { $type = $args['schema']['type']; // For multi-value fields, check the item type instead. if ( 'array' === $type && ! empty( $args['schema']['items']['type'] ) ) { $type = $args['schema']['items']['type']; } switch ( $type ) { case 'string': $value = (string) $value; break; case 'integer': $value = (int) $value; break; case 'number': $value = (float) $value; break; case 'boolean': $value = (bool) $value; break; } // Don't allow objects to be output. if ( is_object( $value ) && ! ( $value instanceof JsonSerializable ) ) { return null; } return $value; } /** * Check the 'meta' value of a request is an associative array. * * @since 4.7.0 * @access public * * @param mixed $value The meta value submitted in the request. * @param WP_REST_Request $request Full details about the request. * @param string $param The parameter name. * @return WP_Error|string The meta array, if valid, otherwise an error. */ public function check_meta_is_array( $value, $request, $param ) { if ( ! is_array( $value ) ) { return false; } return $value; } }