/** * Core Metadata API * * Functions for retrieving and manipulating metadata of various WordPress object types. Metadata * for an object is a represented by a simple key-value pair. Objects may contain multiple * metadata entries that share the same key and differ only in their value. * * @package WordPress * @subpackage Meta */ require ABSPATH . WPINC . '/class-wp-metadata-lazyloader.php'; /** * Adds metadata for the specified object. * * @since 2.9.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. * @param bool $unique Optional. Whether the specified metadata key should be unique for the object. * If true, and the object already has a value for the specified metadata key, * no change will be made. Default false. * @return int|false The meta ID on success, false on failure. */ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = false ) { global $wpdb; if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) ) { return false; } $object_id = absint( $object_id ); if ( ! $object_id ) { return false; } $table = _get_meta_table( $meta_type ); if ( ! $table ) { return false; } $meta_subtype = get_object_subtype( $meta_type, $object_id ); $column = sanitize_key( $meta_type . '_id' ); // expected_slashed ($meta_key) $meta_key = wp_unslash( $meta_key ); $meta_value = wp_unslash( $meta_value ); $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type, $meta_subtype ); /** * Short-circuits adding metadata of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * Returning a non-null value will effectively short-circuit the function. * * Possible hook names include: * * - `add_post_metadata` * - `add_comment_metadata` * - `add_term_metadata` * - `add_user_metadata` * * @since 3.1.0 * * @param null|bool $check Whether to allow adding metadata for the given type. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. * @param bool $unique Whether the specified meta key should be unique for the object. */ $check = apply_filters( "add_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $unique ); if ( null !== $check ) { return $check; } if ( $unique && $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $table WHERE meta_key = %s AND $column = %d", $meta_key, $object_id ) ) ) { return false; } $_meta_value = $meta_value; $meta_value = maybe_serialize( $meta_value ); /** * Fires immediately before meta of a specific type is added. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * * Possible hook names include: * * - `add_post_meta` * - `add_comment_meta` * - `add_term_meta` * - `add_user_meta` * * @since 3.1.0 * * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $_meta_value Metadata value. */ do_action( "add_{$meta_type}_meta", $object_id, $meta_key, $_meta_value ); $result = $wpdb->insert( $table, array( $column => $object_id, 'meta_key' => $meta_key, 'meta_value' => $meta_value, ) ); if ( ! $result ) { return false; } $mid = (int) $wpdb->insert_id; wp_cache_delete( $object_id, $meta_type . '_meta' ); /** * Fires immediately after meta of a specific type is added. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * * Possible hook names include: * * - `added_post_meta` * - `added_comment_meta` * - `added_term_meta` * - `added_user_meta` * * @since 2.9.0 * * @param int $mid The meta ID after successful update. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $_meta_value Metadata value. */ do_action( "added_{$meta_type}_meta", $mid, $object_id, $meta_key, $_meta_value ); return $mid; } /** * Updates metadata for the specified object. If no value already exists for the specified object * ID and metadata key, the metadata will be added. * * @since 2.9.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. * @param mixed $prev_value Optional. Previous value to check before updating. * If specified, only update existing metadata entries with * this value. Otherwise, update all entries. Default empty string. * @return int|bool The new meta field ID if a field with the given key didn't exist * and was therefore added, true on successful update, * false on failure or if the value passed to the function * is the same as the one that is already in the database. */ function update_metadata( $meta_type, $object_id, $meta_key, $meta_value, $prev_value = '' ) { global $wpdb; if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) ) { return false; } $object_id = absint( $object_id ); if ( ! $object_id ) { return false; } $table = _get_meta_table( $meta_type ); if ( ! $table ) { return false; } $meta_subtype = get_object_subtype( $meta_type, $object_id ); $column = sanitize_key( $meta_type . '_id' ); $id_column = ( 'user' === $meta_type ) ? 'umeta_id' : 'meta_id'; // expected_slashed ($meta_key) $raw_meta_key = $meta_key; $meta_key = wp_unslash( $meta_key ); $passed_value = $meta_value; $meta_value = wp_unslash( $meta_value ); $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type, $meta_subtype ); /** * Short-circuits updating metadata of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * Returning a non-null value will effectively short-circuit the function. * * Possible hook names include: * * - `update_post_metadata` * - `update_comment_metadata` * - `update_term_metadata` * - `update_user_metadata` * * @since 3.1.0 * * @param null|bool $check Whether to allow updating metadata for the given type. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. * @param mixed $prev_value Optional. Previous value to check before updating. * If specified, only update existing metadata entries with * this value. Otherwise, update all entries. */ $check = apply_filters( "update_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $prev_value ); if ( null !== $check ) { return (bool) $check; } // Compare existing value to new value if no prev value given and the key exists only once. if ( empty( $prev_value ) ) { $old_value = get_metadata_raw( $meta_type, $object_id, $meta_key ); if ( is_countable( $old_value ) && count( $old_value ) === 1 ) { if ( $old_value[0] === $meta_value ) { return false; } } } $meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT $id_column FROM $table WHERE meta_key = %s AND $column = %d", $meta_key, $object_id ) ); if ( empty( $meta_ids ) ) { return add_metadata( $meta_type, $object_id, $raw_meta_key, $passed_value ); } $_meta_value = $meta_value; $meta_value = maybe_serialize( $meta_value ); $data = compact( 'meta_value' ); $where = array( $column => $object_id, 'meta_key' => $meta_key, ); if ( ! empty( $prev_value ) ) { $prev_value = maybe_serialize( $prev_value ); $where['meta_value'] = $prev_value; } foreach ( $meta_ids as $meta_id ) { /** * Fires immediately before updating metadata of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * * Possible hook names include: * * - `update_post_meta` * - `update_comment_meta` * - `update_term_meta` * - `update_user_meta` * * @since 2.9.0 * * @param int $meta_id ID of the metadata entry to update. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $_meta_value Metadata value. */ do_action( "update_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value ); if ( 'post' === $meta_type ) { /** * Fires immediately before updating a post's metadata. * * @since 2.9.0 * * @param int $meta_id ID of metadata entry to update. * @param int $object_id Post ID. * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value. This will be a PHP-serialized string representation of the value * if the value is an array, an object, or itself a PHP-serialized string. */ do_action( 'update_postmeta', $meta_id, $object_id, $meta_key, $meta_value ); } } $result = $wpdb->update( $table, $data, $where ); if ( ! $result ) { return false; } wp_cache_delete( $object_id, $meta_type . '_meta' ); foreach ( $meta_ids as $meta_id ) { /** * Fires immediately after updating metadata of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * * Possible hook names include: * * - `updated_post_meta` * - `updated_comment_meta` * - `updated_term_meta` * - `updated_user_meta` * * @since 2.9.0 * * @param int $meta_id ID of updated metadata entry. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $_meta_value Metadata value. */ do_action( "updated_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value ); if ( 'post' === $meta_type ) { /** * Fires immediately after updating a post's metadata. * * @since 2.9.0 * * @param int $meta_id ID of updated metadata entry. * @param int $object_id Post ID. * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value. This will be a PHP-serialized string representation of the value * if the value is an array, an object, or itself a PHP-serialized string. */ do_action( 'updated_postmeta', $meta_id, $object_id, $meta_key, $meta_value ); } } return true; } /** * Deletes metadata for the specified object. * * @since 2.9.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $meta_value Optional. Metadata value. Must be serializable if non-scalar. * If specified, only delete metadata entries with this value. * Otherwise, delete all entries with the specified meta_key. * Pass `null`, `false`, or an empty string to skip this check. * (For backward compatibility, it is not possible to pass an empty string * to delete those entries with an empty string for a value.) * Default empty string. * @param bool $delete_all Optional. If true, delete matching metadata entries for all objects, * ignoring the specified object_id. Otherwise, only delete * matching metadata entries for the specified object_id. Default false. * @return bool True on successful delete, false on failure. */ function delete_metadata( $meta_type, $object_id, $meta_key, $meta_value = '', $delete_all = false ) { global $wpdb; if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) && ! $delete_all ) { return false; } $object_id = absint( $object_id ); if ( ! $object_id && ! $delete_all ) { return false; } $table = _get_meta_table( $meta_type ); if ( ! $table ) { return false; } $type_column = sanitize_key( $meta_type . '_id' ); $id_column = ( 'user' === $meta_type ) ? 'umeta_id' : 'meta_id'; // expected_slashed ($meta_key) $meta_key = wp_unslash( $meta_key ); $meta_value = wp_unslash( $meta_value ); /** * Short-circuits deleting metadata of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * Returning a non-null value will effectively short-circuit the function. * * Possible hook names include: * * - `delete_post_metadata` * - `delete_comment_metadata` * - `delete_term_metadata` * - `delete_user_metadata` * * @since 3.1.0 * * @param null|bool $delete Whether to allow metadata deletion of the given type. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. * @param bool $delete_all Whether to delete the matching metadata entries * for all objects, ignoring the specified $object_id. * Default false. */ $check = apply_filters( "delete_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $delete_all ); if ( null !== $check ) { return (bool) $check; } $_meta_value = $meta_value; $meta_value = maybe_serialize( $meta_value ); $query = $wpdb->prepare( "SELECT $id_column FROM $table WHERE meta_key = %s", $meta_key ); if ( ! $delete_all ) { $query .= $wpdb->prepare( " AND $type_column = %d", $object_id ); } if ( '' !== $meta_value && null !== $meta_value && false !== $meta_value ) { $query .= $wpdb->prepare( ' AND meta_value = %s', $meta_value ); } $meta_ids = $wpdb->get_col( $query ); if ( ! count( $meta_ids ) ) { return false; } if ( $delete_all ) { if ( '' !== $meta_value && null !== $meta_value && false !== $meta_value ) { $object_ids = $wpdb->get_col( $wpdb->prepare( "SELECT $type_column FROM $table WHERE meta_key = %s AND meta_value = %s", $meta_key, $meta_value ) ); } else { $object_ids = $wpdb->get_col( $wpdb->prepare( "SELECT $type_column FROM $table WHERE meta_key = %s", $meta_key ) ); } } /** * Fires immediately before deleting metadata of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * * Possible hook names include: * * - `delete_post_meta` * - `delete_comment_meta` * - `delete_term_meta` * - `delete_user_meta` * * @since 3.1.0 * * @param string[] $meta_ids An array of metadata entry IDs to delete. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $_meta_value Metadata value. */ do_action( "delete_{$meta_type}_meta", $meta_ids, $object_id, $meta_key, $_meta_value ); // Old-style action. if ( 'post' === $meta_type ) { /** * Fires immediately before deleting metadata for a post. * * @since 2.9.0 * * @param string[] $meta_ids An array of metadata entry IDs to delete. */ do_action( 'delete_postmeta', $meta_ids ); } $query = "DELETE FROM $table WHERE $id_column IN( " . implode( ',', $meta_ids ) . ' )'; $count = $wpdb->query( $query ); if ( ! $count ) { return false; } if ( $delete_all ) { $data = (array) $object_ids; } else { $data = array( $object_id ); } wp_cache_delete_multiple( $data, $meta_type . '_meta' ); /** * Fires immediately after deleting metadata of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * * Possible hook names include: * * - `deleted_post_meta` * - `deleted_comment_meta` * - `deleted_term_meta` * - `deleted_user_meta` * * @since 2.9.0 * * @param string[] $meta_ids An array of metadata entry IDs to delete. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $_meta_value Metadata value. */ do_action( "deleted_{$meta_type}_meta", $meta_ids, $object_id, $meta_key, $_meta_value ); // Old-style action. if ( 'post' === $meta_type ) { /** * Fires immediately after deleting metadata for a post. * * @since 2.9.0 * * @param string[] $meta_ids An array of metadata entry IDs to delete. */ do_action( 'deleted_postmeta', $meta_ids ); } return true; } /** * Retrieves the value of a metadata field for the specified object type and ID. * * If the meta field exists, a single value is returned if `$single` is true, * or an array of values if it's false. * * If the meta field does not exist, the result depends on get_metadata_default(). * By default, an empty string is returned if `$single` is true, or an empty array * if it's false. * * @since 2.9.0 * * @see get_metadata_raw() * @see get_metadata_default() * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Optional. Metadata key. If not specified, retrieve all metadata for * the specified object. Default empty string. * @param bool $single Optional. If true, return only the first value of the specified `$meta_key`. * This parameter has no effect if `$meta_key` is not specified. Default false. * @return mixed An array of values if `$single` is false. * The value of the meta field if `$single` is true. * False for an invalid `$object_id` (non-numeric, zero, or negative value), * or if `$meta_type` is not specified. * An empty string if a valid but non-existing object ID is passed. */ function get_metadata( $meta_type, $object_id, $meta_key = '', $single = false ) { $value = get_metadata_raw( $meta_type, $object_id, $meta_key, $single ); if ( ! is_null( $value ) ) { return $value; } return get_metadata_default( $meta_type, $object_id, $meta_key, $single ); } /** * Retrieves raw metadata value for the specified object. * * @since 5.5.0 * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Optional. Metadata key. If not specified, retrieve all metadata for * the specified object. Default empty string. * @param bool $single Optional. If true, return only the first value of the specified `$meta_key`. * This parameter has no effect if `$meta_key` is not specified. Default false. * @return mixed An array of values if `$single` is false. * The value of the meta field if `$single` is true. * False for an invalid `$object_id` (non-numeric, zero, or negative value), * or if `$meta_type` is not specified. * Null if the value does not exist. */ function get_metadata_raw( $meta_type, $object_id, $meta_key = '', $single = false ) { if ( ! $meta_type || ! is_numeric( $object_id ) ) { return false; } $object_id = absint( $object_id ); if ( ! $object_id ) { return false; } /** * Short-circuits the return value of a meta field. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * Returning a non-null value will effectively short-circuit the function. * * Possible filter names include: * * - `get_post_metadata` * - `get_comment_metadata` * - `get_term_metadata` * - `get_user_metadata` * * @since 3.1.0 * @since 5.5.0 Added the `$meta_type` parameter. * * @param mixed $value The value to return, either a single metadata value or an array * of values depending on the value of `$single`. Default null. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param bool $single Whether to return only the first value of the specified `$meta_key`. * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. */ $check = apply_filters( "get_{$meta_type}_metadata", null, $object_id, $meta_key, $single, $meta_type ); if ( null !== $check ) { if ( $single && is_array( $check ) ) { return $check[0]; } else { return $check; } } $meta_cache = wp_cache_get( $object_id, $meta_type . '_meta' ); if ( ! $meta_cache ) { $meta_cache = update_meta_cache( $meta_type, array( $object_id ) ); if ( isset( $meta_cache[ $object_id ] ) ) { $meta_cache = $meta_cache[ $object_id ]; } else { $meta_cache = null; } } if ( ! $meta_key ) { return $meta_cache; } if ( isset( $meta_cache[ $meta_key ] ) ) { if ( $single ) { return maybe_unserialize( $meta_cache[ $meta_key ][0] ); } else { return array_map( 'maybe_unserialize', $meta_cache[ $meta_key ] ); } } return null; } /** * Retrieves default metadata value for the specified meta key and object. * * By default, an empty string is returned if `$single` is true, or an empty array * if it's false. * * @since 5.5.0 * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param bool $single Optional. If true, return only the first value of the specified `$meta_key`. * This parameter has no effect if `$meta_key` is not specified. Default false. * @return mixed An array of default values if `$single` is false. * The default value of the meta field if `$single` is true. */ function get_metadata_default( $meta_type, $object_id, $meta_key, $single = false ) { if ( $single ) { $value = ''; } else { $value = array(); } /** * Filters the default metadata value for a specified meta key and object. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * * Possible filter names include: * * - `default_post_metadata` * - `default_comment_metadata` * - `default_term_metadata` * - `default_user_metadata` * * @since 5.5.0 * * @param mixed $value The value to return, either a single metadata value or an array * of values depending on the value of `$single`. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param bool $single Whether to return only the first value of the specified `$meta_key`. * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. */ $value = apply_filters( "default_{$meta_type}_metadata", $value, $object_id, $meta_key, $single, $meta_type ); if ( ! $single && ! wp_is_numeric_array( $value ) ) { $value = array( $value ); } return $value; } /** * Determines if a meta field with the given key exists for the given object ID. * * @since 3.3.0 * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @return bool Whether a meta field with the given key exists. */ function metadata_exists( $meta_type, $object_id, $meta_key ) { if ( ! $meta_type || ! is_numeric( $object_id ) ) { return false; } $object_id = absint( $object_id ); if ( ! $object_id ) { return false; } /** This filter is documented in wp-includes/meta.php */ $check = apply_filters( "get_{$meta_type}_metadata", null, $object_id, $meta_key, true, $meta_type ); if ( null !== $check ) { return (bool) $check; } $meta_cache = wp_cache_get( $object_id, $meta_type . '_meta' ); if ( ! $meta_cache ) { $meta_cache = update_meta_cache( $meta_type, array( $object_id ) ); $meta_cache = $meta_cache[ $object_id ]; } if ( isset( $meta_cache[ $meta_key ] ) ) { return true; } return false; } /** * Retrieves metadata by meta ID. * * @since 3.3.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $meta_id ID for a specific meta row. * @return stdClass|false { * Metadata object, or boolean `false` if the metadata doesn't exist. * * @type string $meta_key The meta key. * @type mixed $meta_value The unserialized meta value. * @type string $meta_id Optional. The meta ID when the meta type is any value except 'user'. * @type string $umeta_id Optional. The meta ID when the meta type is 'user'. * @type string $post_id Optional. The object ID when the meta type is 'post'. * @type string $comment_id Optional. The object ID when the meta type is 'comment'. * @type string $term_id Optional. The object ID when the meta type is 'term'. * @type string $user_id Optional. The object ID when the meta type is 'user'. * } */ function get_metadata_by_mid( $meta_type, $meta_id ) { global $wpdb; if ( ! $meta_type || ! is_numeric( $meta_id ) || floor( $meta_id ) != $meta_id ) { return false; } $meta_id = (int) $meta_id; if ( $meta_id <= 0 ) { return false; } $table = _get_meta_table( $meta_type ); if ( ! $table ) { return false; } /** * Short-circuits the return value when fetching a meta field by meta ID. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * Returning a non-null value will effectively short-circuit the function. * * Possible hook names include: * * - `get_post_metadata_by_mid` * - `get_comment_metadata_by_mid` * - `get_term_metadata_by_mid` * - `get_user_metadata_by_mid` * * @since 5.0.0 * * @param stdClass|null $value The value to return. * @param int $meta_id Meta ID. */ $check = apply_filters( "get_{$meta_type}_metadata_by_mid", null, $meta_id ); if ( null !== $check ) { return $check; } $id_column = ( 'user' === $meta_type ) ? 'umeta_id' : 'meta_id'; $meta = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table WHERE $id_column = %d", $meta_id ) ); if ( empty( $meta ) ) { return false; } if ( isset( $meta->meta_value ) ) { $meta->meta_value = maybe_unserialize( $meta->meta_value ); } return $meta; } /** * Updates metadata by meta ID. * * @since 3.3.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $meta_id ID for a specific meta row. * @param string $meta_value Metadata value. Must be serializable if non-scalar. * @param string|false $meta_key Optional. You can provide a meta key to update it. Default false. * @return bool True on successful update, false on failure. */ function update_metadata_by_mid( $meta_type, $meta_id, $meta_value, $meta_key = false ) { global $wpdb; // Make sure everything is valid. if ( ! $meta_type || ! is_numeric( $meta_id ) || floor( $meta_id ) != $meta_id ) { return false; } $meta_id = (int) $meta_id; if ( $meta_id <= 0 ) { return false; } $table = _get_meta_table( $meta_type ); if ( ! $table ) { return false; } $column = sanitize_key( $meta_type . '_id' ); $id_column = ( 'user' === $meta_type ) ? 'umeta_id' : 'meta_id'; /** * Short-circuits updating metadata of a specific type by meta ID. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * Returning a non-null value will effectively short-circuit the function. * * Possible hook names include: * * - `update_post_metadata_by_mid` * - `update_comment_metadata_by_mid` * - `update_term_metadata_by_mid` * - `update_user_metadata_by_mid` * * @since 5.0.0 * * @param null|bool $check Whether to allow updating metadata for the given type. * @param int $meta_id Meta ID. * @param mixed $meta_value Meta value. Must be serializable if non-scalar. * @param string|false $meta_key Meta key, if provided. */ $check = apply_filters( "update_{$meta_type}_metadata_by_mid", null, $meta_id, $meta_value, $meta_key ); if ( null !== $check ) { return (bool) $check; } // Fetch the meta and go on if it's found. $meta = get_metadata_by_mid( $meta_type, $meta_id ); if ( $meta ) { $original_key = $meta->meta_key; $object_id = $meta->{$column}; /* * If a new meta_key (last parameter) was specified, change the meta key, * otherwise use the original key in the update statement. */ if ( false === $meta_key ) { $meta_key = $original_key; } elseif ( ! is_string( $meta_key ) ) { return false; } $meta_subtype = get_object_subtype( $meta_type, $object_id ); // Sanitize the meta. $_meta_value = $meta_value; $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type, $meta_subtype ); $meta_value = maybe_serialize( $meta_value ); // Format the data query arguments. $data = array( 'meta_key' => $meta_key, 'meta_value' => $meta_value, ); // Format the where query arguments. $where = array(); $where[ $id_column ] = $meta_id; /** This action is documented in wp-includes/meta.php */ do_action( "update_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value ); if ( 'post' === $meta_type ) { /** This action is documented in wp-includes/meta.php */ do_action( 'update_postmeta', $meta_id, $object_id, $meta_key, $meta_value ); } // Run the update query, all fields in $data are %s, $where is a %d. $result = $wpdb->update( $table, $data, $where, '%s', '%d' ); if ( ! $result ) { return false; } // Clear the caches. wp_cache_delete( $object_id, $meta_type . '_meta' ); /** This action is documented in wp-includes/meta.php */ do_action( "updated_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value ); if ( 'post' === $meta_type ) { /** This action is documented in wp-includes/meta.php */ do_action( 'updated_postmeta', $meta_id, $object_id, $meta_key, $meta_value ); } return true; } // And if the meta was not found. return false; } /** * Deletes metadata by meta ID. * * @since 3.3.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $meta_id ID for a specific meta row. * @return bool True on successful delete, false on failure. */ function delete_metadata_by_mid( $meta_type, $meta_id ) { global $wpdb; // Make sure everything is valid. if ( ! $meta_type || ! is_numeric( $meta_id ) || floor( $meta_id ) != $meta_id ) { return false; } $meta_id = (int) $meta_id; if ( $meta_id <= 0 ) { return false; } $table = _get_meta_table( $meta_type ); if ( ! $table ) { return false; } // Object and ID columns. $column = sanitize_key( $meta_type . '_id' ); $id_column = ( 'user' === $meta_type ) ? 'umeta_id' : 'meta_id'; /** * Short-circuits deleting metadata of a specific type by meta ID. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * Returning a non-null value will effectively short-circuit the function. * * Possible hook names include: * * - `delete_post_metadata_by_mid` * - `delete_comment_metadata_by_mid` * - `delete_term_metadata_by_mid` * - `delete_user_metadata_by_mid` * * @since 5.0.0 * * @param null|bool $delete Whether to allow metadata deletion of the given type. * @param int $meta_id Meta ID. */ $check = apply_filters( "delete_{$meta_type}_metadata_by_mid", null, $meta_id ); if ( null !== $check ) { return (bool) $check; } // Fetch the meta and go on if it's found. $meta = get_metadata_by_mid( $meta_type, $meta_id ); if ( $meta ) { $object_id = (int) $meta->{$column}; /** This action is documented in wp-includes/meta.php */ do_action( "delete_{$meta_type}_meta", (array) $meta_id, $object_id, $meta->meta_key, $meta->meta_value ); // Old-style action. if ( 'post' === $meta_type || 'comment' === $meta_type ) { /** * Fires immediately before deleting post or comment metadata of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta * object type (post or comment). * * Possible hook names include: * * - `delete_postmeta` * - `delete_commentmeta` * - `delete_termmeta` * - `delete_usermeta` * * @since 3.4.0 * * @param int $meta_id ID of the metadata entry to delete. */ do_action( "delete_{$meta_type}meta", $meta_id ); } // Run the query, will return true if deleted, false otherwise. $result = (bool) $wpdb->delete( $table, array( $id_column => $meta_id ) ); // Clear the caches. wp_cache_delete( $object_id, $meta_type . '_meta' ); /** This action is documented in wp-includes/meta.php */ do_action( "deleted_{$meta_type}_meta", (array) $meta_id, $object_id, $meta->meta_key, $meta->meta_value ); // Old-style action. if ( 'post' === $meta_type || 'comment' === $meta_type ) { /** * Fires immediately after deleting post or comment metadata of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta * object type (post or comment). * * Possible hook names include: * * - `deleted_postmeta` * - `deleted_commentmeta` * - `deleted_termmeta` * - `deleted_usermeta` * * @since 3.4.0 * * @param int $meta_id Deleted metadata entry ID. */ do_action( "deleted_{$meta_type}meta", $meta_id ); } return $result; } // Meta ID was not found. return false; } /** * Updates the metadata cache for the specified objects. * * @since 2.9.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param string|int[] $object_ids Array or comma delimited list of object IDs to update cache for. * @return array|false Metadata cache for the specified objects, or false on failure. */ function update_meta_cache( $meta_type, $object_ids ) { global $wpdb; if ( ! $meta_type || ! $object_ids ) { return false; } $table = _get_meta_table( $meta_type ); if ( ! $table ) { return false; } $column = sanitize_key( $meta_type . '_id' ); if ( ! is_array( $object_ids ) ) { $object_ids = preg_replace( '|[^0-9,]|', '', $object_ids ); $object_ids = explode( ',', $object_ids ); } $object_ids = array_map( 'intval', $object_ids ); /** * Short-circuits updating the metadata cache of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * Returning a non-null value will effectively short-circuit the function. * * Possible hook names include: * * - `update_post_metadata_cache` * - `update_comment_metadata_cache` * - `update_term_metadata_cache` * - `update_user_metadata_cache` * * @since 5.0.0 * * @param mixed $check Whether to allow updating the meta cache of the given type. * @param int[] $object_ids Array of object IDs to update the meta cache for. */ $check = apply_filters( "update_{$meta_type}_metadata_cache", null, $object_ids ); if ( null !== $check ) { return (bool) $check; } $cache_key = $meta_type . '_meta'; $non_cached_ids = array(); $cache = array(); $cache_values = wp_cache_get_multiple( $object_ids, $cache_key ); foreach ( $cache_values as $id => $cached_object ) { if ( false === $cached_object ) { $non_cached_ids[] = $id; } else { $cache[ $id ] = $cached_object; } } if ( empty( $non_cached_ids ) ) { return $cache; } // Get meta info. $id_list = implode( ',', $non_cached_ids ); $id_column = ( 'user' === $meta_type ) ? 'umeta_id' : 'meta_id'; $meta_list = $wpdb->get_results( "SELECT $column, meta_key, meta_value FROM $table WHERE $column IN ($id_list) ORDER BY $id_column ASC", ARRAY_A ); if ( ! empty( $meta_list ) ) { foreach ( $meta_list as $metarow ) { $mpid = (int) $metarow[ $column ]; $mkey = $metarow['meta_key']; $mval = $metarow['meta_value']; // Force subkeys to be array type. if ( ! isset( $cache[ $mpid ] ) || ! is_array( $cache[ $mpid ] ) ) { $cache[ $mpid ] = array(); } if ( ! isset( $cache[ $mpid ][ $mkey ] ) || ! is_array( $cache[ $mpid ][ $mkey ] ) ) { $cache[ $mpid ][ $mkey ] = array(); } // Add a value to the current pid/key. $cache[ $mpid ][ $mkey ][] = $mval; } } $data = array(); foreach ( $non_cached_ids as $id ) { if ( ! isset( $cache[ $id ] ) ) { $cache[ $id ] = array(); } $data[ $id ] = $cache[ $id ]; } wp_cache_add_multiple( $data, $cache_key ); return $cache; } /** * Retrieves the queue for lazy-loading metadata. * * @since 4.5.0 * * @return WP_Metadata_Lazyloader Metadata lazyloader queue. */ function wp_metadata_lazyloader() { static $wp_metadata_lazyloader; if ( null === $wp_metadata_lazyloader ) { $wp_metadata_lazyloader = new WP_Metadata_Lazyloader(); } return $wp_metadata_lazyloader; } /** * Given a meta query, generates SQL clauses to be appended to a main query. * * @since 3.2.0 * * @see WP_Meta_Query * * @param array $meta_query A meta query. * @param string $type Type of meta. * @param string $primary_table Primary database table name. * @param string $primary_id_column Primary ID column name. * @param object $context Optional. The main query object. Default null. * @return string[]|false { * Array containing JOIN and WHERE SQL clauses to append to the main query, * or false if no table exists for the requested meta type. * * @type string $join SQL fragment to append to the main JOIN clause. * @type string $where SQL fragment to append to the main WHERE clause. * } */ function get_meta_sql( $meta_query, $type, $primary_table, $primary_id_column, $context = null ) { $meta_query_obj = new WP_Meta_Query( $meta_query ); return $meta_query_obj->get_sql( $type, $primary_table, $primary_id_column, $context ); } /** * Retrieves the name of the metadata table for the specified object type. * * @since 2.9.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @return string|false Metadata table name, or false if no metadata table exists */ function _get_meta_table( $type ) { global $wpdb; $table_name = $type . 'meta'; if ( empty( $wpdb->$table_name ) ) { return false; } return $wpdb->$table_name; } /** * Determines whether a meta key is considered protected. * * @since 3.1.3 * * @param string $meta_key Metadata key. * @param string $meta_type Optional. Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. Default empty string. * @return bool Whether the meta key is considered protected. */ function is_protected_meta( $meta_key, $meta_type = '' ) { $sanitized_key = preg_replace( "/[^\x20-\x7E\p{L}]/", '', $meta_key ); $protected = strlen( $sanitized_key ) > 0 && ( '_' === $sanitized_key[0] ); /** * Filters whether a meta key is considered protected. * * @since 3.2.0 * * @param bool $protected Whether the key is considered protected. * @param string $meta_key Metadata key. * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. */ return apply_filters( 'is_protected_meta', $protected, $meta_key, $meta_type ); } /** * Sanitizes meta value. * * @since 3.1.3 * @since 4.9.8 The `$object_subtype` parameter was added. * * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value to sanitize. * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param string $object_subtype Optional. The subtype of the object type. Default empty string. * @return mixed Sanitized $meta_value. */ function sanitize_meta( $meta_key, $meta_value, $object_type, $object_subtype = '' ) { if ( ! empty( $object_subtype ) && has_filter( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}" ) ) { /** * Filters the sanitization of a specific meta key of a specific meta type and subtype. * * The dynamic portions of the hook name, `$object_type`, `$meta_key`, * and `$object_subtype`, refer to the metadata object type (comment, post, term, or user), * the meta key value, and the object subtype respectively. * * @since 4.9.8 * * @param mixed $meta_value Metadata value to sanitize. * @param string $meta_key Metadata key. * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param string $object_subtype Object subtype. */ return apply_filters( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $meta_value, $meta_key, $object_type, $object_subtype ); } /** * Filters the sanitization of a specific meta key of a specific meta type. * * The dynamic portions of the hook name, `$meta_type`, and `$meta_key`, * refer to the metadata object type (comment, post, term, or user) and the meta * key value, respectively. * * @since 3.3.0 * * @param mixed $meta_value Metadata value to sanitize. * @param string $meta_key Metadata key. * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. */ return apply_filters( "sanitize_{$object_type}_meta_{$meta_key}", $meta_value, $meta_key, $object_type ); } /** * Registers a meta key. * * It is recommended to register meta keys for a specific combination of object type and object subtype. If passing * an object subtype is omitted, the meta key will be registered for the entire object type, however it can be partly * overridden in case a more specific meta key of the same name exists for the same object type and a subtype. * * If an object type does not support any subtypes, such as users or comments, you should commonly call this function * without passing a subtype. * * @since 3.3.0 * @since 4.6.0 {@link https://core.trac.wordpress.org/ticket/35658 Modified * to support an array of data to attach to registered meta keys}. Previous arguments for * `$sanitize_callback` and `$auth_callback` have been folded into this array. * @since 4.9.8 The `$object_subtype` argument was added to the arguments array. * @since 5.3.0 Valid meta types expanded to include "array" and "object". * @since 5.5.0 The `$default` argument was added to the arguments array. * @since 6.4.0 The `$revisions_enabled` argument was added to the arguments array. * * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param string $meta_key Meta key to register. * @param array $args { * Data used to describe the meta key when registered. * * @type string $object_subtype A subtype; e.g. if the object type is "post", the post type. If left empty, * the meta key will be registered on the entire object type. Default empty. * @type string $type The type of data associated with this meta key. * Valid values are 'string', 'boolean', 'integer', 'number', 'array', and 'object'. * @type string $description A description of the data attached to this meta key. * @type bool $single Whether the meta key has one value per object, or an array of values per object. * @type mixed $default The default value returned from get_metadata() if no value has been set yet. * When using a non-single meta key, the default value is for the first entry. * In other words, when calling get_metadata() with `$single` set to `false`, * the default value given here will be wrapped in an array. * @type callable $sanitize_callback A function or method to call when sanitizing `$meta_key` data. * @type callable $auth_callback Optional. A function or method to call when performing edit_post_meta, * add_post_meta, and delete_post_meta capability checks. * @type bool|array $show_in_rest Whether data associated with this meta key can be considered public and * should be accessible via the REST API. A custom post type must also declare * support for custom fields for registered meta to be accessible via REST. * When registering complex meta values this argument may optionally be an * array with 'schema' or 'prepare_callback' keys instead of a boolean. * @type bool $revisions_enabled Whether to enable revisions support for this meta_key. Can only be used when the * object type is 'post'. * } * @param string|array $deprecated Deprecated. Use `$args` instead. * @return bool True if the meta key was successfully registered in the global array, false if not. * Registering a meta key with distinct sanitize and auth callbacks will fire those callbacks, * but will not add to the global registry. */ function register_meta( $object_type, $meta_key, $args, $deprecated = null ) { global $wp_meta_keys; if ( ! is_array( $wp_meta_keys ) ) { $wp_meta_keys = array(); } $defaults = array( 'object_subtype' => '', 'type' => 'string', 'description' => '', 'default' => '', 'single' => false, 'sanitize_callback' => null, 'auth_callback' => null, 'show_in_rest' => false, 'revisions_enabled' => false, ); // There used to be individual args for sanitize and auth callbacks. $has_old_sanitize_cb = false; $has_old_auth_cb = false; if ( is_callable( $args ) ) { $args = array( 'sanitize_callback' => $args, ); $has_old_sanitize_cb = true; } else { $args = (array) $args; } if ( is_callable( $deprecated ) ) { $args['auth_callback'] = $deprecated; $has_old_auth_cb = true; } /** * Filters the registration arguments when registering meta. * * @since 4.6.0 * * @param array $args Array of meta registration arguments. * @param array $defaults Array of default arguments. * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param string $meta_key Meta key. */ $args = apply_filters( 'register_meta_args', $args, $defaults, $object_type, $meta_key ); unset( $defaults['default'] ); $args = wp_parse_args( $args, $defaults ); // Require an item schema when registering array meta. if ( false !== $args['show_in_rest'] && 'array' === $args['type'] ) { if ( ! is_array( $args['show_in_rest'] ) || ! isset( $args['show_in_rest']['schema']['items'] ) ) { _doing_it_wrong( __FUNCTION__, __( 'When registering an "array" meta type to show in the REST API, you must specify the schema for each array item in "show_in_rest.schema.items".' ), '5.3.0' ); return false; } } $object_subtype = ! empty( $args['object_subtype'] ) ? $args['object_subtype'] : ''; if ( $args['revisions_enabled'] ) { if ( 'post' !== $object_type ) { _doing_it_wrong( __FUNCTION__, __( 'Meta keys cannot enable revisions support unless the object type supports revisions.' ), '6.4.0' ); return false; } elseif ( ! empty( $object_subtype ) && ! post_type_supports( $object_subtype, 'revisions' ) ) { _doing_it_wrong( __FUNCTION__, __( 'Meta keys cannot enable revisions support unless the object subtype supports revisions.' ), '6.4.0' ); return false; } } // If `auth_callback` is not provided, fall back to `is_protected_meta()`. if ( empty( $args['auth_callback'] ) ) { if ( is_protected_meta( $meta_key, $object_type ) ) { $args['auth_callback'] = '__return_false'; } else { $args['auth_callback'] = '__return_true'; } } // Back-compat: old sanitize and auth callbacks are applied to all of an object type. if ( is_callable( $args['sanitize_callback'] ) ) { if ( ! empty( $object_subtype ) ) { add_filter( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['sanitize_callback'], 10, 4 ); } else { add_filter( "sanitize_{$object_type}_meta_{$meta_key}", $args['sanitize_callback'], 10, 3 ); } } if ( is_callable( $args['auth_callback'] ) ) { if ( ! empty( $object_subtype ) ) { add_filter( "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['auth_callback'], 10, 6 ); } else { add_filter( "auth_{$object_type}_meta_{$meta_key}", $args['auth_callback'], 10, 6 ); } } if ( array_key_exists( 'default', $args ) ) { $schema = $args; if ( is_array( $args['show_in_rest'] ) && isset( $args['show_in_rest']['schema'] ) ) { $schema = array_merge( $schema, $args['show_in_rest']['schema'] ); } $check = rest_validate_value_from_schema( $args['default'], $schema ); if ( is_wp_error( $check ) ) { _doing_it_wrong( __FUNCTION__, __( 'When registering a default meta value the data must match the type provided.' ), '5.5.0' ); return false; } if ( ! has_filter( "default_{$object_type}_metadata", 'filter_default_metadata' ) ) { add_filter( "default_{$object_type}_metadata", 'filter_default_metadata', 10, 5 ); } } // Global registry only contains meta keys registered with the array of arguments added in 4.6.0. if ( ! $has_old_auth_cb && ! $has_old_sanitize_cb ) { unset( $args['object_subtype'] ); $wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ] = $args; return true; } return false; } /** * Filters into default_{$object_type}_metadata and adds in default value. * * @since 5.5.0 * * @param mixed $value Current value passed to filter. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param bool $single If true, return only the first value of the specified `$meta_key`. * This parameter has no effect if `$meta_key` is not specified. * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @return mixed An array of default values if `$single` is false. * The default value of the meta field if `$single` is true. */ function filter_default_metadata( $value, $object_id, $meta_key, $single, $meta_type ) { global $wp_meta_keys; if ( wp_installing() ) { return $value; } if ( ! is_array( $wp_meta_keys ) || ! isset( $wp_meta_keys[ $meta_type ] ) ) { return $value; } $defaults = array(); foreach ( $wp_meta_keys[ $meta_type ] as $sub_type => $meta_data ) { foreach ( $meta_data as $_meta_key => $args ) { if ( $_meta_key === $meta_key && array_key_exists( 'default', $args ) ) { $defaults[ $sub_type ] = $args; } } } if ( ! $defaults ) { return $value; } // If this meta type does not have subtypes, then the default is keyed as an empty string. if ( isset( $defaults[''] ) ) { $metadata = $defaults['']; } else { $sub_type = get_object_subtype( $meta_type, $object_id ); if ( ! isset( $defaults[ $sub_type ] ) ) { return $value; } $metadata = $defaults[ $sub_type ]; } if ( $single ) { $value = $metadata['default']; } else { $value = array( $metadata['default'] ); } return $value; } /** * Checks if a meta key is registered. * * @since 4.6.0 * @since 4.9.8 The `$object_subtype` parameter was added. * * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param string $meta_key Metadata key. * @param string $object_subtype Optional. The subtype of the object type. Default empty string. * @return bool True if the meta key is registered to the object type and, if provided, * the object subtype. False if not. */ function registered_meta_key_exists( $object_type, $meta_key, $object_subtype = '' ) { $meta_keys = get_registered_meta_keys( $object_type, $object_subtype ); return isset( $meta_keys[ $meta_key ] ); } /** * Unregisters a meta key from the list of registered keys. * * @since 4.6.0 * @since 4.9.8 The `$object_subtype` parameter was added. * * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param string $meta_key Metadata key. * @param string $object_subtype Optional. The subtype of the object type. Default empty string. * @return bool True if successful. False if the meta key was not registered. */ function unregister_meta_key( $object_type, $meta_key, $object_subtype = '' ) { global $wp_meta_keys; if ( ! registered_meta_key_exists( $object_type, $meta_key, $object_subtype ) ) { return false; } $args = $wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ]; if ( isset( $args['sanitize_callback'] ) && is_callable( $args['sanitize_callback'] ) ) { if ( ! empty( $object_subtype ) ) { remove_filter( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['sanitize_callback'] ); } else { remove_filter( "sanitize_{$object_type}_meta_{$meta_key}", $args['sanitize_callback'] ); } } if ( isset( $args['auth_callback'] ) && is_callable( $args['auth_callback'] ) ) { if ( ! empty( $object_subtype ) ) { remove_filter( "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['auth_callback'] ); } else { remove_filter( "auth_{$object_type}_meta_{$meta_key}", $args['auth_callback'] ); } } unset( $wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ] ); // Do some clean up. if ( empty( $wp_meta_keys[ $object_type ][ $object_subtype ] ) ) { unset( $wp_meta_keys[ $object_type ][ $object_subtype ] ); } if ( empty( $wp_meta_keys[ $object_type ] ) ) { unset( $wp_meta_keys[ $object_type ] ); } return true; } /** * Retrieves a list of registered metadata args for an object type, keyed by their meta keys. * * @since 4.6.0 * @since 4.9.8 The `$object_subtype` parameter was added. * * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param string $object_subtype Optional. The subtype of the object type. Default empty string. * @return array[] List of registered metadata args, keyed by their meta keys. */ function get_registered_meta_keys( $object_type, $object_subtype = '' ) { global $wp_meta_keys; if ( ! is_array( $wp_meta_keys ) || ! isset( $wp_meta_keys[ $object_type ] ) || ! isset( $wp_meta_keys[ $object_type ][ $object_subtype ] ) ) { return array(); } return $wp_meta_keys[ $object_type ][ $object_subtype ]; } /** * Retrieves registered metadata for a specified object. * * The results include both meta that is registered specifically for the * object's subtype and meta that is registered for the entire object type. * * @since 4.6.0 * * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $object_id ID of the object the metadata is for. * @param string $meta_key Optional. Registered metadata key. If not specified, retrieve all registered * metadata for the specified object. * @return mixed A single value or array of values for a key if specified. An array of all registered keys * and values for an object ID if not. False if a given $meta_key is not registered. */ function get_registered_metadata( $object_type, $object_id, $meta_key = '' ) { $object_subtype = get_object_subtype( $object_type, $object_id ); if ( ! empty( $meta_key ) ) { if ( ! empty( $object_subtype ) && ! registered_meta_key_exists( $object_type, $meta_key, $object_subtype ) ) { $object_subtype = ''; } if ( ! registered_meta_key_exists( $object_type, $meta_key, $object_subtype ) ) { return false; } $meta_keys = get_registered_meta_keys( $object_type, $object_subtype ); $meta_key_data = $meta_keys[ $meta_key ]; $data = get_metadata( $object_type, $object_id, $meta_key, $meta_key_data['single'] ); return $data; } $data = get_metadata( $object_type, $object_id ); if ( ! $data ) { return array(); } $meta_keys = get_registered_meta_keys( $object_type ); if ( ! empty( $object_subtype ) ) { $meta_keys = array_merge( $meta_keys, get_registered_meta_keys( $object_type, $object_subtype ) ); } return array_intersect_key( $data, $meta_keys ); } /** * Filters out `register_meta()` args based on an allowed list. * * `register_meta()` args may change over time, so requiring the allowed list * to be explicitly turned off is a warranty seal of sorts. * * @access private * @since 5.5.0 * * @param array $args Arguments from `register_meta()`. * @param array $default_args Default arguments for `register_meta()`. * @return array Filtered arguments. */ function _wp_register_meta_args_allowed_list( $args, $default_args ) { return array_intersect_key( $args, $default_args ); } /** * Returns the object subtype for a given object ID of a specific type. * * @since 4.9.8 * * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $object_id ID of the object to retrieve its subtype. * @return string The object subtype or an empty string if unspecified subtype. */ function get_object_subtype( $object_type, $object_id ) { $object_id = (int) $object_id; $object_subtype = ''; switch ( $object_type ) { case 'post': $post_type = get_post_type( $object_id ); if ( ! empty( $post_type ) ) { $object_subtype = $post_type; } break; case 'term': $term = get_term( $object_id ); if ( ! $term instanceof WP_Term ) { break; } $object_subtype = $term->taxonomy; break; case 'comment': $comment = get_comment( $object_id ); if ( ! $comment ) { break; } $object_subtype = 'comment'; break; case 'user': $user = get_user_by( 'id', $object_id ); if ( ! $user ) { break; } $object_subtype = 'user'; break; } /** * Filters the object subtype identifier for a non-standard object type. * * The dynamic portion of the hook name, `$object_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * * Possible hook names include: * * - `get_object_subtype_post` * - `get_object_subtype_comment` * - `get_object_subtype_term` * - `get_object_subtype_user` * * @since 4.9.8 * * @param string $object_subtype Empty string to override. * @param int $object_id ID of the object to get the subtype for. */ return apply_filters( "get_object_subtype_{$object_type}", $object_subtype, $object_id ); } /** * Sets up the default filters and actions for most * of the WordPress hooks. * * If you need to remove a default hook, this file will * give you the priority to use for removing the hook. * * Not all of the default hooks are found in this file. * For instance, administration-related hooks are located in * wp-admin/includes/admin-filters.php. * * If a hook should only be called from a specific context * (admin area, multisite environment…), please move it * to a more appropriate file instead. * * @package WordPress */ // Strip, trim, kses, special chars for string saves. foreach ( array( 'pre_term_name', 'pre_comment_author_name', 'pre_link_name', 'pre_link_target', 'pre_link_rel', 'pre_user_display_name', 'pre_user_first_name', 'pre_user_last_name', 'pre_user_nickname' ) as $filter ) { add_filter( $filter, 'sanitize_text_field' ); add_filter( $filter, 'wp_filter_kses' ); add_filter( $filter, '_wp_specialchars', 30 ); } // Strip, kses, special chars for string display. foreach ( array( 'term_name', 'comment_author_name', 'link_name', 'link_target', 'link_rel', 'user_display_name', 'user_first_name', 'user_last_name', 'user_nickname' ) as $filter ) { if ( is_admin() ) { // These are expensive. Run only on admin pages for defense in depth. add_filter( $filter, 'sanitize_text_field' ); add_filter( $filter, 'wp_kses_data' ); } add_filter( $filter, '_wp_specialchars', 30 ); } // Kses only for textarea saves. foreach ( array( 'pre_term_description', 'pre_link_description', 'pre_link_notes', 'pre_user_description' ) as $filter ) { add_filter( $filter, 'wp_filter_kses' ); } // Kses only for textarea admin displays. if ( is_admin() ) { foreach ( array( 'term_description', 'link_description', 'link_notes', 'user_description' ) as $filter ) { add_filter( $filter, 'wp_kses_data' ); } add_filter( 'comment_text', 'wp_kses_post' ); } // Email saves. foreach ( array( 'pre_comment_author_email', 'pre_user_email' ) as $filter ) { add_filter( $filter, 'trim' ); add_filter( $filter, 'sanitize_email' ); add_filter( $filter, 'wp_filter_kses' ); } // Email admin display. foreach ( array( 'comment_author_email', 'user_email' ) as $filter ) { add_filter( $filter, 'sanitize_email' ); if ( is_admin() ) { add_filter( $filter, 'wp_kses_data' ); } } // Save URL. foreach ( array( 'pre_comment_author_url', 'pre_user_url', 'pre_link_url', 'pre_link_image', 'pre_link_rss', 'pre_post_guid', ) as $filter ) { add_filter( $filter, 'wp_strip_all_tags' ); add_filter( $filter, 'sanitize_url' ); add_filter( $filter, 'wp_filter_kses' ); } // Display URL. foreach ( array( 'user_url', 'link_url', 'link_image', 'link_rss', 'comment_url', 'post_guid' ) as $filter ) { if ( is_admin() ) { add_filter( $filter, 'wp_strip_all_tags' ); } add_filter( $filter, 'esc_url' ); if ( is_admin() ) { add_filter( $filter, 'wp_kses_data' ); } } // Slugs. add_filter( 'pre_term_slug', 'sanitize_title' ); add_filter( 'wp_insert_post_data', '_wp_customize_changeset_filter_insert_post_data', 10, 2 ); // Keys. foreach ( array( 'pre_post_type', 'pre_post_status', 'pre_post_comment_status', 'pre_post_ping_status' ) as $filter ) { add_filter( $filter, 'sanitize_key' ); } // Mime types. add_filter( 'pre_post_mime_type', 'sanitize_mime_type' ); add_filter( 'post_mime_type', 'sanitize_mime_type' ); // Meta. add_filter( 'register_meta_args', '_wp_register_meta_args_allowed_list', 10, 2 ); // Counts. add_action( 'admin_init', 'wp_schedule_update_user_counts' ); add_action( 'wp_update_user_counts', 'wp_schedule_update_user_counts', 10, 0 ); foreach ( array( 'user_register', 'deleted_user' ) as $action ) { add_action( $action, 'wp_maybe_update_user_counts', 10, 0 ); } // Post meta. add_action( 'added_post_meta', 'wp_cache_set_posts_last_changed' ); add_action( 'updated_post_meta', 'wp_cache_set_posts_last_changed' ); add_action( 'deleted_post_meta', 'wp_cache_set_posts_last_changed' ); // User meta. add_action( 'added_user_meta', 'wp_cache_set_users_last_changed' ); add_action( 'updated_user_meta', 'wp_cache_set_users_last_changed' ); add_action( 'deleted_user_meta', 'wp_cache_set_users_last_changed' ); add_action( 'add_user_role', 'wp_cache_set_users_last_changed' ); add_action( 'set_user_role', 'wp_cache_set_users_last_changed' ); add_action( 'remove_user_role', 'wp_cache_set_users_last_changed' ); // Term meta. add_action( 'added_term_meta', 'wp_cache_set_terms_last_changed' ); add_action( 'updated_term_meta', 'wp_cache_set_terms_last_changed' ); add_action( 'deleted_term_meta', 'wp_cache_set_terms_last_changed' ); add_filter( 'get_term_metadata', 'wp_check_term_meta_support_prefilter' ); add_filter( 'add_term_metadata', 'wp_check_term_meta_support_prefilter' ); add_filter( 'update_term_metadata', 'wp_check_term_meta_support_prefilter' ); add_filter( 'delete_term_metadata', 'wp_check_term_meta_support_prefilter' ); add_filter( 'get_term_metadata_by_mid', 'wp_check_term_meta_support_prefilter' ); add_filter( 'update_term_metadata_by_mid', 'wp_check_term_meta_support_prefilter' ); add_filter( 'delete_term_metadata_by_mid', 'wp_check_term_meta_support_prefilter' ); add_filter( 'update_term_metadata_cache', 'wp_check_term_meta_support_prefilter' ); // Comment meta. add_action( 'added_comment_meta', 'wp_cache_set_comments_last_changed' ); add_action( 'updated_comment_meta', 'wp_cache_set_comments_last_changed' ); add_action( 'deleted_comment_meta', 'wp_cache_set_comments_last_changed' ); // Places to balance tags on input. foreach ( array( 'content_save_pre', 'excerpt_save_pre', 'comment_save_pre', 'pre_comment_content' ) as $filter ) { add_filter( $filter, 'convert_invalid_entities' ); add_filter( $filter, 'balanceTags', 50 ); } // Add proper rel values for links with target. add_action( 'init', 'wp_init_targeted_link_rel_filters' ); // Format strings for display. foreach ( array( 'comment_author', 'term_name', 'link_name', 'link_description', 'link_notes', 'bloginfo', 'wp_title', 'document_title', 'widget_title' ) as $filter ) { add_filter( $filter, 'wptexturize' ); add_filter( $filter, 'convert_chars' ); add_filter( $filter, 'esc_html' ); } // Format WordPress. foreach ( array( 'the_content', 'the_title', 'wp_title', 'document_title' ) as $filter ) { add_filter( $filter, 'capital_P_dangit', 11 ); } add_filter( 'comment_text', 'capital_P_dangit', 31 ); // Format titles. foreach ( array( 'single_post_title', 'single_cat_title', 'single_tag_title', 'single_month_title', 'nav_menu_attr_title', 'nav_menu_description' ) as $filter ) { add_filter( $filter, 'wptexturize' ); add_filter( $filter, 'strip_tags' ); } // Format text area for display. foreach ( array( 'term_description', 'get_the_post_type_description' ) as $filter ) { add_filter( $filter, 'wptexturize' ); add_filter( $filter, 'convert_chars' ); add_filter( $filter, 'wpautop' ); add_filter( $filter, 'shortcode_unautop' ); } // Format for RSS. add_filter( 'term_name_rss', 'convert_chars' ); // Pre save hierarchy. add_filter( 'wp_insert_post_parent', 'wp_check_post_hierarchy_for_loops', 10, 2 ); add_filter( 'wp_update_term_parent', 'wp_check_term_hierarchy_for_loops', 10, 3 ); // Display filters. add_filter( 'the_title', 'wptexturize' ); add_filter( 'the_title', 'convert_chars' ); add_filter( 'the_title', 'trim' ); add_filter( 'the_content', 'do_blocks', 9 ); add_filter( 'the_content', 'wptexturize' ); add_filter( 'the_content', 'convert_smilies', 20 ); add_filter( 'the_content', 'wpautop' ); add_filter( 'the_content', 'shortcode_unautop' ); add_filter( 'the_content', 'prepend_attachment' ); add_filter( 'the_content', 'wp_replace_insecure_home_url' ); add_filter( 'the_content', 'do_shortcode', 11 ); // AFTER wpautop(). add_filter( 'the_content', 'wp_filter_content_tags', 12 ); // Runs after do_shortcode(). add_filter( 'the_excerpt', 'wptexturize' ); add_filter( 'the_excerpt', 'convert_smilies' ); add_filter( 'the_excerpt', 'convert_chars' ); add_filter( 'the_excerpt', 'wpautop' ); add_filter( 'the_excerpt', 'shortcode_unautop' ); add_filter( 'the_excerpt', 'wp_replace_insecure_home_url' ); add_filter( 'the_excerpt', 'wp_filter_content_tags', 12 ); add_filter( 'get_the_excerpt', 'wp_trim_excerpt', 10, 2 ); add_filter( 'the_post_thumbnail_caption', 'wptexturize' ); add_filter( 'the_post_thumbnail_caption', 'convert_smilies' ); add_filter( 'the_post_thumbnail_caption', 'convert_chars' ); add_filter( 'comment_text', 'wptexturize' ); add_filter( 'comment_text', 'convert_chars' ); add_filter( 'comment_text', 'make_clickable', 9 ); add_filter( 'comment_text', 'force_balance_tags', 25 ); add_filter( 'comment_text', 'convert_smilies', 20 ); add_filter( 'comment_text', 'wpautop', 30 ); add_filter( 'comment_excerpt', 'convert_chars' ); add_filter( 'list_cats', 'wptexturize' ); add_filter( 'wp_sprintf', 'wp_sprintf_l', 10, 2 ); add_filter( 'widget_text', 'balanceTags' ); add_filter( 'widget_text_content', 'capital_P_dangit', 11 ); add_filter( 'widget_text_content', 'wptexturize' ); add_filter( 'widget_text_content', 'convert_smilies', 20 ); add_filter( 'widget_text_content', 'wpautop' ); add_filter( 'widget_text_content', 'shortcode_unautop' ); add_filter( 'widget_text_content', 'wp_replace_insecure_home_url' ); add_filter( 'widget_text_content', 'do_shortcode', 11 ); // Runs after wpautop(); note that $post global will be null when shortcodes run. add_filter( 'widget_text_content', 'wp_filter_content_tags', 12 ); // Runs after do_shortcode(). add_filter( 'widget_block_content', 'do_blocks', 9 ); add_filter( 'widget_block_content', 'do_shortcode', 11 ); add_filter( 'widget_block_content', 'wp_filter_content_tags', 12 ); // Runs after do_shortcode(). add_filter( 'block_type_metadata', 'wp_migrate_old_typography_shape' ); add_filter( 'wp_get_custom_css', 'wp_replace_insecure_home_url' ); // RSS filters. add_filter( 'the_title_rss', 'strip_tags' ); add_filter( 'the_title_rss', 'ent2ncr', 8 ); add_filter( 'the_title_rss', 'esc_html' ); add_filter( 'the_content_rss', 'ent2ncr', 8 ); add_filter( 'the_content_feed', 'wp_staticize_emoji' ); add_filter( 'the_content_feed', '_oembed_filter_feed_content' ); add_filter( 'the_excerpt_rss', 'convert_chars' ); add_filter( 'the_excerpt_rss', 'ent2ncr', 8 ); add_filter( 'comment_author_rss', 'ent2ncr', 8 ); add_filter( 'comment_text_rss', 'ent2ncr', 8 ); add_filter( 'comment_text_rss', 'esc_html' ); add_filter( 'comment_text_rss', 'wp_staticize_emoji' ); add_filter( 'bloginfo_rss', 'ent2ncr', 8 ); add_filter( 'the_author', 'ent2ncr', 8 ); add_filter( 'the_guid', 'esc_url' ); // Email filters. add_filter( 'wp_mail', 'wp_staticize_emoji_for_email' ); // Robots filters. add_filter( 'wp_robots', 'wp_robots_noindex' ); add_filter( 'wp_robots', 'wp_robots_noindex_embeds' ); add_filter( 'wp_robots', 'wp_robots_noindex_search' ); add_filter( 'wp_robots', 'wp_robots_max_image_preview_large' ); // Mark site as no longer fresh. foreach ( array( 'publish_post', 'publish_page', 'wp_ajax_save-widget', 'wp_ajax_widgets-order', 'customize_save_after', 'rest_after_save_widget', 'rest_delete_widget', 'rest_save_sidebar', ) as $action ) { add_action( $action, '_delete_option_fresh_site', 0 ); } // Misc filters. add_filter( 'option_ping_sites', 'privacy_ping_filter' ); add_filter( 'option_blog_charset', '_wp_specialchars' ); // IMPORTANT: This must not be wp_specialchars() or esc_html() or it'll cause an infinite loop. add_filter( 'option_blog_charset', '_canonical_charset' ); add_filter( 'option_home', '_config_wp_home' ); add_filter( 'option_siteurl', '_config_wp_siteurl' ); add_filter( 'tiny_mce_before_init', '_mce_set_direction' ); add_filter( 'teeny_mce_before_init', '_mce_set_direction' ); add_filter( 'pre_kses', 'wp_pre_kses_less_than' ); add_filter( 'pre_kses', 'wp_pre_kses_block_attributes', 10, 3 ); add_filter( 'sanitize_title', 'sanitize_title_with_dashes', 10, 3 ); add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 ); add_filter( 'comment_flood_filter', 'wp_throttle_comment_flood', 10, 3 ); add_filter( 'pre_comment_content', 'wp_rel_ugc', 15 ); add_filter( 'comment_email', 'antispambot' ); add_filter( 'option_tag_base', '_wp_filter_taxonomy_base' ); add_filter( 'option_category_base', '_wp_filter_taxonomy_base' ); add_filter( 'the_posts', '_close_comments_for_old_posts', 10, 2 ); add_filter( 'comments_open', '_close_comments_for_old_post', 10, 2 ); add_filter( 'pings_open', '_close_comments_for_old_post', 10, 2 ); add_filter( 'editable_slug', 'urldecode' ); add_filter( 'editable_slug', 'esc_textarea' ); add_filter( 'pingback_ping_source_uri', 'pingback_ping_source_uri' ); add_filter( 'xmlrpc_pingback_error', 'xmlrpc_pingback_error' ); add_filter( 'title_save_pre', 'trim' ); add_action( 'transition_comment_status', '_clear_modified_cache_on_transition_comment_status', 10, 2 ); add_filter( 'http_request_host_is_external', 'allowed_http_request_hosts', 10, 2 ); // REST API filters. add_action( 'xmlrpc_rsd_apis', 'rest_output_rsd' ); add_action( 'wp_head', 'rest_output_link_wp_head', 10, 0 ); add_action( 'template_redirect', 'rest_output_link_header', 11, 0 ); add_action( 'auth_cookie_malformed', 'rest_cookie_collect_status' ); add_action( 'auth_cookie_expired', 'rest_cookie_collect_status' ); add_action( 'auth_cookie_bad_username', 'rest_cookie_collect_status' ); add_action( 'auth_cookie_bad_hash', 'rest_cookie_collect_status' ); add_action( 'auth_cookie_valid', 'rest_cookie_collect_status' ); add_action( 'application_password_failed_authentication', 'rest_application_password_collect_status' ); add_action( 'application_password_did_authenticate', 'rest_application_password_collect_status', 10, 2 ); add_filter( 'rest_authentication_errors', 'rest_application_password_check_errors', 90 ); add_filter( 'rest_authentication_errors', 'rest_cookie_check_errors', 100 ); // Actions. add_action( 'wp_head', '_wp_render_title_tag', 1 ); add_action( 'wp_head', 'wp_enqueue_scripts', 1 ); add_action( 'wp_head', 'wp_resource_hints', 2 ); add_action( 'wp_head', 'wp_preload_resources', 1 ); add_action( 'wp_head', 'feed_links', 2 ); add_action( 'wp_head', 'feed_links_extra', 3 ); add_action( 'wp_head', 'rsd_link' ); add_action( 'wp_head', 'locale_stylesheet' ); add_action( 'publish_future_post', 'check_and_publish_future_post', 10, 1 ); add_action( 'wp_head', 'wp_robots', 1 ); add_action( 'wp_head', 'print_emoji_detection_script', 7 ); add_action( 'wp_head', 'wp_print_styles', 8 ); add_action( 'wp_head', 'wp_print_head_scripts', 9 ); add_action( 'wp_head', 'wp_generator' ); add_action( 'wp_head', 'rel_canonical' ); add_action( 'wp_head', 'wp_shortlink_wp_head', 10, 0 ); add_action( 'wp_head', 'wp_custom_css_cb', 101 ); add_action( 'wp_head', 'wp_site_icon', 99 ); add_action( 'wp_footer', 'wp_print_footer_scripts', 20 ); add_action( 'template_redirect', 'wp_shortlink_header', 11, 0 ); add_action( 'wp_print_footer_scripts', '_wp_footer_scripts' ); add_action( 'init', '_register_core_block_patterns_and_categories' ); add_action( 'init', 'check_theme_switched', 99 ); add_action( 'init', array( 'WP_Block_Supports', 'init' ), 22 ); add_action( 'switch_theme', 'wp_clean_theme_json_cache' ); add_action( 'start_previewing_theme', 'wp_clean_theme_json_cache' ); add_action( 'after_switch_theme', '_wp_menus_changed' ); add_action( 'after_switch_theme', '_wp_sidebars_changed' ); add_action( 'wp_enqueue_scripts', 'wp_enqueue_emoji_styles' ); add_action( 'wp_print_styles', 'print_emoji_styles' ); // Retained for backwards-compatibility. Unhooked by wp_enqueue_emoji_styles(). if ( isset( $_GET['replytocom'] ) ) { add_filter( 'wp_robots', 'wp_robots_no_robots' ); } // Login actions. add_action( 'login_head', 'wp_robots', 1 ); add_filter( 'login_head', 'wp_resource_hints', 8 ); add_action( 'login_head', 'wp_print_head_scripts', 9 ); add_action( 'login_head', 'print_admin_styles', 9 ); add_action( 'login_head', 'wp_site_icon', 99 ); add_action( 'login_footer', 'wp_print_footer_scripts', 20 ); add_action( 'login_init', 'send_frame_options_header', 10, 0 ); // Feed generator tags. foreach ( array( 'rss2_head', 'commentsrss2_head', 'rss_head', 'rdf_header', 'atom_head', 'comments_atom_head', 'opml_head', 'app_head' ) as $action ) { add_action( $action, 'the_generator' ); } // Feed Site Icon. add_action( 'atom_head', 'atom_site_icon' ); add_action( 'rss2_head', 'rss2_site_icon' ); // WP Cron. if ( ! defined( 'DOING_CRON' ) ) { add_action( 'init', 'wp_cron' ); } // HTTPS migration. add_action( 'update_option_home', 'wp_update_https_migration_required', 10, 2 ); // 2 Actions 2 Furious. add_action( 'do_feed_rdf', 'do_feed_rdf', 10, 0 ); add_action( 'do_feed_rss', 'do_feed_rss', 10, 0 ); add_action( 'do_feed_rss2', 'do_feed_rss2', 10, 1 ); add_action( 'do_feed_atom', 'do_feed_atom', 10, 1 ); add_action( 'do_pings', 'do_all_pings', 10, 0 ); add_action( 'do_all_pings', 'do_all_pingbacks', 10, 0 ); add_action( 'do_all_pings', 'do_all_enclosures', 10, 0 ); add_action( 'do_all_pings', 'do_all_trackbacks', 10, 0 ); add_action( 'do_all_pings', 'generic_ping', 10, 0 ); add_action( 'do_robots', 'do_robots' ); add_action( 'do_favicon', 'do_favicon' ); add_action( 'set_comment_cookies', 'wp_set_comment_cookies', 10, 3 ); add_action( 'sanitize_comment_cookies', 'sanitize_comment_cookies' ); add_action( 'init', 'smilies_init', 5 ); add_action( 'plugins_loaded', 'wp_maybe_load_widgets', 0 ); add_action( 'plugins_loaded', 'wp_maybe_load_embeds', 0 ); add_action( 'shutdown', 'wp_ob_end_flush_all', 1 ); // Create a revision whenever a post is updated. add_action( 'wp_after_insert_post', 'wp_save_post_revision_on_insert', 9, 3 ); add_action( 'post_updated', 'wp_save_post_revision', 10, 1 ); add_action( 'publish_post', '_publish_post_hook', 5, 1 ); add_action( 'transition_post_status', '_transition_post_status', 5, 3 ); add_action( 'transition_post_status', '_update_term_count_on_transition_post_status', 10, 3 ); add_action( 'comment_form', 'wp_comment_form_unfiltered_html_nonce' ); // Privacy. add_action( 'user_request_action_confirmed', '_wp_privacy_account_request_confirmed' ); add_action( 'user_request_action_confirmed', '_wp_privacy_send_request_confirmation_notification', 12 ); // After request marked as completed. add_filter( 'wp_privacy_personal_data_exporters', 'wp_register_comment_personal_data_exporter' ); add_filter( 'wp_privacy_personal_data_exporters', 'wp_register_media_personal_data_exporter' ); add_filter( 'wp_privacy_personal_data_exporters', 'wp_register_user_personal_data_exporter', 1 ); add_filter( 'wp_privacy_personal_data_erasers', 'wp_register_comment_personal_data_eraser' ); add_action( 'init', 'wp_schedule_delete_old_privacy_export_files' ); add_action( 'wp_privacy_delete_old_export_files', 'wp_privacy_delete_old_export_files' ); // Cron tasks. add_action( 'wp_scheduled_delete', 'wp_scheduled_delete' ); add_action( 'wp_scheduled_auto_draft_delete', 'wp_delete_auto_drafts' ); add_action( 'importer_scheduled_cleanup', 'wp_delete_attachment' ); add_action( 'upgrader_scheduled_cleanup', 'wp_delete_attachment' ); add_action( 'delete_expired_transients', 'delete_expired_transients' ); // Navigation menu actions. add_action( 'delete_post', '_wp_delete_post_menu_item' ); add_action( 'delete_term', '_wp_delete_tax_menu_item', 10, 3 ); add_action( 'transition_post_status', '_wp_auto_add_pages_to_menu', 10, 3 ); add_action( 'delete_post', '_wp_delete_customize_changeset_dependent_auto_drafts' ); // Post Thumbnail specific image filtering. add_action( 'begin_fetch_post_thumbnail_html', '_wp_post_thumbnail_class_filter_add' ); add_action( 'end_fetch_post_thumbnail_html', '_wp_post_thumbnail_class_filter_remove' ); add_action( 'begin_fetch_post_thumbnail_html', '_wp_post_thumbnail_context_filter_add' ); add_action( 'end_fetch_post_thumbnail_html', '_wp_post_thumbnail_context_filter_remove' ); // Redirect old slugs. add_action( 'template_redirect', 'wp_old_slug_redirect' ); add_action( 'post_updated', 'wp_check_for_changed_slugs', 12, 3 ); add_action( 'attachment_updated', 'wp_check_for_changed_slugs', 12, 3 ); // Redirect old dates. add_action( 'post_updated', 'wp_check_for_changed_dates', 12, 3 ); add_action( 'attachment_updated', 'wp_check_for_changed_dates', 12, 3 ); // Nonce check for post previews. add_action( 'init', '_show_post_preview' ); // Output JS to reset window.name for previews. add_action( 'wp_head', 'wp_post_preview_js', 1 ); // Timezone. add_filter( 'pre_option_gmt_offset', 'wp_timezone_override_offset' ); // If the upgrade hasn't run yet, assume link manager is used. add_filter( 'default_option_link_manager_enabled', '__return_true' ); // This option no longer exists; tell plugins we always support auto-embedding. add_filter( 'pre_option_embed_autourls', '__return_true' ); // Default settings for heartbeat. add_filter( 'heartbeat_settings', 'wp_heartbeat_settings' ); // Check if the user is logged out. add_action( 'admin_enqueue_scripts', 'wp_auth_check_load' ); add_filter( 'heartbeat_send', 'wp_auth_check' ); add_filter( 'heartbeat_nopriv_send', 'wp_auth_check' ); // Default authentication filters. add_filter( 'authenticate', 'wp_authenticate_username_password', 20, 3 ); add_filter( 'authenticate', 'wp_authenticate_email_password', 20, 3 ); add_filter( 'authenticate', 'wp_authenticate_application_password', 20, 3 ); add_filter( 'authenticate', 'wp_authenticate_spam_check', 99 ); add_filter( 'determine_current_user', 'wp_validate_auth_cookie' ); add_filter( 'determine_current_user', 'wp_validate_logged_in_cookie', 20 ); add_filter( 'determine_current_user', 'wp_validate_application_password', 20 ); // Split term updates. add_action( 'admin_init', '_wp_check_for_scheduled_split_terms' ); add_action( 'split_shared_term', '_wp_check_split_default_terms', 10, 4 ); add_action( 'split_shared_term', '_wp_check_split_terms_in_menus', 10, 4 ); add_action( 'split_shared_term', '_wp_check_split_nav_menu_terms', 10, 4 ); add_action( 'wp_split_shared_term_batch', '_wp_batch_split_terms' ); // Comment type updates. add_action( 'admin_init', '_wp_check_for_scheduled_update_comment_type' ); add_action( 'wp_update_comment_type_batch', '_wp_batch_update_comment_type' ); // Email notifications. add_action( 'comment_post', 'wp_new_comment_notify_moderator' ); add_action( 'comment_post', 'wp_new_comment_notify_postauthor' ); add_action( 'after_password_reset', 'wp_password_change_notification' ); add_action( 'register_new_user', 'wp_send_new_user_notifications' ); add_action( 'edit_user_created_user', 'wp_send_new_user_notifications', 10, 2 ); // REST API actions. add_action( 'init', 'rest_api_init' ); add_action( 'rest_api_init', 'rest_api_default_filters', 10, 1 ); add_action( 'rest_api_init', 'register_initial_settings', 10 ); add_action( 'rest_api_init', 'create_initial_rest_routes', 99 ); add_action( 'parse_request', 'rest_api_loaded' ); // Sitemaps actions. add_action( 'init', 'wp_sitemaps_get_server' ); /** * Filters formerly mixed into wp-includes. */ // Theme. add_action( 'setup_theme', 'create_initial_theme_features', 0 ); add_action( 'after_setup_theme', '_add_default_theme_supports', 1 ); add_action( 'wp_loaded', '_custom_header_background_just_in_time' ); add_action( 'wp_head', '_custom_logo_header_styles' ); add_action( 'plugins_loaded', '_wp_customize_include' ); add_action( 'transition_post_status', '_wp_customize_publish_changeset', 10, 3 ); add_action( 'admin_enqueue_scripts', '_wp_customize_loader_settings' ); add_action( 'delete_attachment', '_delete_attachment_theme_mod' ); add_action( 'transition_post_status', '_wp_keep_alive_customize_changeset_dependent_auto_drafts', 20, 3 ); // Block Theme Previews. add_action( 'plugins_loaded', 'wp_initialize_theme_preview_hooks', 1 ); // Calendar widget cache. add_action( 'save_post', 'delete_get_calendar_cache' ); add_action( 'delete_post', 'delete_get_calendar_cache' ); add_action( 'update_option_start_of_week', 'delete_get_calendar_cache' ); add_action( 'update_option_gmt_offset', 'delete_get_calendar_cache' ); // Author. add_action( 'transition_post_status', '__clear_multi_author_cache' ); // Post. add_action( 'init', 'create_initial_post_types', 0 ); // Highest priority. add_action( 'admin_menu', '_add_post_type_submenus' ); add_action( 'before_delete_post', '_reset_front_page_settings_for_post' ); add_action( 'wp_trash_post', '_reset_front_page_settings_for_post' ); add_action( 'change_locale', 'create_initial_post_types' ); // Post Formats. add_filter( 'request', '_post_format_request' ); add_filter( 'term_link', '_post_format_link', 10, 3 ); add_filter( 'get_post_format', '_post_format_get_term' ); add_filter( 'get_terms', '_post_format_get_terms', 10, 3 ); add_filter( 'wp_get_object_terms', '_post_format_wp_get_object_terms' ); // KSES. add_action( 'init', 'kses_init' ); add_action( 'set_current_user', 'kses_init' ); // Script Loader. add_action( 'wp_default_scripts', 'wp_default_scripts' ); add_action( 'wp_default_scripts', 'wp_default_packages' ); add_action( 'wp_enqueue_scripts', 'wp_localize_jquery_ui_datepicker', 1000 ); add_action( 'wp_enqueue_scripts', 'wp_common_block_scripts_and_styles' ); add_action( 'wp_enqueue_scripts', 'wp_enqueue_classic_theme_styles' ); add_action( 'admin_enqueue_scripts', 'wp_localize_jquery_ui_datepicker', 1000 ); add_action( 'admin_enqueue_scripts', 'wp_common_block_scripts_and_styles' ); add_action( 'enqueue_block_assets', 'wp_enqueue_registered_block_scripts_and_styles' ); add_action( 'enqueue_block_assets', 'enqueue_block_styles_assets', 30 ); /* * `wp_enqueue_registered_block_scripts_and_styles` is bound to both * `enqueue_block_editor_assets` and `enqueue_block_assets` hooks * since the introduction of the block editor in WordPress 5.0. * * The way this works is that the block assets are loaded before any other assets. * For example, this is the order of styles for the editor: * * - front styles registered for blocks, via `styles` handle (block.json) * - editor styles registered for blocks, via `editorStyles` handle (block.json) * - editor styles enqueued via `enqueue_block_editor_assets` hook * - front styles enqueued via `enqueue_block_assets` hook */ add_action( 'enqueue_block_editor_assets', 'wp_enqueue_registered_block_scripts_and_styles' ); add_action( 'enqueue_block_editor_assets', 'enqueue_editor_block_styles_assets' ); add_action( 'enqueue_block_editor_assets', 'wp_enqueue_editor_block_directory_assets' ); add_action( 'enqueue_block_editor_assets', 'wp_enqueue_editor_format_library_assets' ); add_action( 'enqueue_block_editor_assets', 'wp_enqueue_global_styles_css_custom_properties' ); add_action( 'wp_print_scripts', 'wp_just_in_time_script_localization' ); add_filter( 'print_scripts_array', 'wp_prototype_before_jquery' ); add_action( 'customize_controls_print_styles', 'wp_resource_hints', 1 ); add_action( 'admin_head', 'wp_check_widget_editor_deps' ); add_filter( 'block_editor_settings_all', 'wp_add_editor_classic_theme_styles' ); // Global styles can be enqueued in both the header and the footer. See https://core.trac.wordpress.org/ticket/53494. add_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles' ); add_action( 'wp_footer', 'wp_enqueue_global_styles', 1 ); // Global styles custom CSS. add_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles_custom_css' ); // Block supports, and other styles parsed and stored in the Style Engine. add_action( 'wp_enqueue_scripts', 'wp_enqueue_stored_styles' ); add_action( 'wp_footer', 'wp_enqueue_stored_styles', 1 ); add_action( 'wp_default_styles', 'wp_default_styles' ); add_filter( 'style_loader_src', 'wp_style_loader_src', 10, 2 ); add_action( 'wp_head', 'wp_maybe_inline_styles', 1 ); // Run for styles enqueued in
. add_action( 'wp_footer', 'wp_maybe_inline_styles', 1 ); // Run for late-loaded styles in the footer. /* * Block specific actions and filters. */ // Footnotes Block. add_action( 'init', '_wp_footnotes_kses_init' ); add_action( 'set_current_user', '_wp_footnotes_kses_init' ); add_filter( 'force_filtered_html_on_import', '_wp_footnotes_force_filtered_html_on_import_filter', 999 ); /* * Disable "Post Attributes" for wp_navigation post type. The attributes are * also conditionally enabled when a site has custom templates. Block Theme * templates can be available for every post type. */ add_filter( 'theme_wp_navigation_templates', '__return_empty_array' ); // Taxonomy. add_action( 'init', 'create_initial_taxonomies', 0 ); // Highest priority. add_action( 'change_locale', 'create_initial_taxonomies' ); // Canonical. add_action( 'template_redirect', 'redirect_canonical' ); add_action( 'template_redirect', 'wp_redirect_admin_locations', 1000 ); // Media. add_action( 'wp_playlist_scripts', 'wp_playlist_scripts' ); add_action( 'customize_controls_enqueue_scripts', 'wp_plupload_default_settings' ); add_action( 'plugins_loaded', '_wp_add_additional_image_sizes', 0 ); add_filter( 'plupload_default_settings', 'wp_show_heic_upload_error' ); // Nav menu. add_filter( 'nav_menu_item_id', '_nav_menu_item_id_use_once', 10, 2 ); add_filter( 'nav_menu_css_class', 'wp_nav_menu_remove_menu_item_has_children_class', 10, 4 ); // Widgets. add_action( 'after_setup_theme', 'wp_setup_widgets_block_editor', 1 ); add_action( 'init', 'wp_widgets_init', 1 ); add_action( 'change_locale', array( 'WP_Widget_Media', 'reset_default_labels' ) ); add_action( 'widgets_init', '_wp_block_theme_register_classic_sidebars', 1 ); // Admin Bar. // Don't remove. Wrong way to disable. add_action( 'template_redirect', '_wp_admin_bar_init', 0 ); add_action( 'admin_init', '_wp_admin_bar_init' ); add_action( 'wp_enqueue_scripts', 'wp_enqueue_admin_bar_bump_styles' ); add_action( 'wp_enqueue_scripts', 'wp_enqueue_admin_bar_header_styles' ); add_action( 'admin_enqueue_scripts', 'wp_enqueue_admin_bar_header_styles' ); add_action( 'before_signup_header', '_wp_admin_bar_init' ); add_action( 'activate_header', '_wp_admin_bar_init' ); add_action( 'wp_body_open', 'wp_admin_bar_render', 0 ); add_action( 'wp_footer', 'wp_admin_bar_render', 1000 ); // Back-compat for themes not using `wp_body_open`. add_action( 'in_admin_header', 'wp_admin_bar_render', 0 ); // Former admin filters that can also be hooked on the front end. add_action( 'media_buttons', 'media_buttons' ); add_filter( 'image_send_to_editor', 'image_add_caption', 20, 8 ); add_filter( 'media_send_to_editor', 'image_media_send_to_editor', 10, 3 ); // Embeds. add_action( 'rest_api_init', 'wp_oembed_register_route' ); add_filter( 'rest_pre_serve_request', '_oembed_rest_pre_serve_request', 10, 4 ); add_action( 'wp_head', 'wp_oembed_add_discovery_links' ); add_action( 'wp_head', 'wp_oembed_add_host_js' ); // Back-compat for sites disabling oEmbed host JS by removing action. add_filter( 'embed_oembed_html', 'wp_maybe_enqueue_oembed_host_js' ); add_action( 'embed_head', 'enqueue_embed_scripts', 1 ); add_action( 'embed_head', 'print_emoji_detection_script' ); add_action( 'embed_head', 'wp_enqueue_embed_styles', 9 ); add_action( 'embed_head', 'print_embed_styles' ); // Retained for backwards-compatibility. Unhooked by wp_enqueue_embed_styles(). add_action( 'embed_head', 'wp_print_head_scripts', 20 ); add_action( 'embed_head', 'wp_print_styles', 20 ); add_action( 'embed_head', 'wp_robots' ); add_action( 'embed_head', 'rel_canonical' ); add_action( 'embed_head', 'locale_stylesheet', 30 ); add_action( 'enqueue_embed_scripts', 'wp_enqueue_emoji_styles' ); add_action( 'embed_content_meta', 'print_embed_comments_button' ); add_action( 'embed_content_meta', 'print_embed_sharing_button' ); add_action( 'embed_footer', 'print_embed_sharing_dialog' ); add_action( 'embed_footer', 'print_embed_scripts' ); add_action( 'embed_footer', 'wp_print_footer_scripts', 20 ); add_filter( 'excerpt_more', 'wp_embed_excerpt_more', 20 ); add_filter( 'the_excerpt_embed', 'wptexturize' ); add_filter( 'the_excerpt_embed', 'convert_chars' ); add_filter( 'the_excerpt_embed', 'wpautop' ); add_filter( 'the_excerpt_embed', 'shortcode_unautop' ); add_filter( 'the_excerpt_embed', 'wp_embed_excerpt_attachment' ); add_filter( 'oembed_dataparse', 'wp_filter_oembed_iframe_title_attribute', 5, 3 ); add_filter( 'oembed_dataparse', 'wp_filter_oembed_result', 10, 3 ); add_filter( 'oembed_response_data', 'get_oembed_response_data_rich', 10, 4 ); add_filter( 'pre_oembed_result', 'wp_filter_pre_oembed_result', 10, 3 ); // Capabilities. add_filter( 'user_has_cap', 'wp_maybe_grant_install_languages_cap', 1 ); add_filter( 'user_has_cap', 'wp_maybe_grant_resume_extensions_caps', 1 ); add_filter( 'user_has_cap', 'wp_maybe_grant_site_health_caps', 1, 4 ); // Block templates post type and rendering. add_filter( 'render_block_context', '_block_template_render_without_post_block_context' ); add_filter( 'pre_wp_unique_post_slug', 'wp_filter_wp_template_unique_post_slug', 10, 5 ); add_action( 'save_post_wp_template_part', 'wp_set_unique_slug_on_create_template_part' ); add_action( 'wp_enqueue_scripts', 'wp_enqueue_block_template_skip_link' ); add_action( 'wp_footer', 'the_block_template_skip_link' ); // Retained for backwards-compatibility. Unhooked by wp_enqueue_block_template_skip_link(). add_action( 'after_setup_theme', 'wp_enable_block_templates', 1 ); add_action( 'wp_loaded', '_add_template_loader_filters' ); // wp_navigation post type. add_filter( 'rest_wp_navigation_item_schema', array( 'WP_Navigation_Fallback', 'update_wp_navigation_post_schema' ) ); // Fluid typography. add_filter( 'render_block', 'wp_render_typography_support', 10, 2 ); // User preferences. add_action( 'init', 'wp_register_persisted_preferences_meta' ); // CPT wp_block custom postmeta field. add_action( 'init', 'wp_create_initial_post_meta' ); // Include revisioned meta when considering whether a post revision has changed. add_filter( 'wp_save_post_revision_post_has_changed', 'wp_check_revisioned_meta_fields_have_changed', 10, 3 ); // Save revisioned post meta immediately after a revision is saved add_action( '_wp_put_post_revision', 'wp_save_revisioned_meta_fields', 10, 2 ); // Include revisioned meta when creating or updating an autosave revision. add_action( 'wp_creating_autosave', 'wp_autosave_post_revisioned_meta_fields' ); // When restoring revisions, also restore revisioned meta. add_action( 'wp_restore_post_revision', 'wp_restore_post_revision_meta', 10, 2 ); // Font management. add_action( 'wp_head', 'wp_print_font_faces', 50 ); add_action( 'deleted_post', '_wp_after_delete_font_family', 10, 2 ); add_action( 'before_delete_post', '_wp_before_delete_font_face', 10, 2 ); add_action( 'init', '_wp_register_default_font_collections' ); // Add ignoredHookedBlocks metadata attribute to the template and template part post types. add_filter( 'rest_pre_insert_wp_template', 'inject_ignored_hooked_blocks_metadata_attributes' ); add_filter( 'rest_pre_insert_wp_template_part', 'inject_ignored_hooked_blocks_metadata_attributes' ); unset( $filter, $action ); /** * A class for displaying various tree-like structures. * * Extend the Walker class to use it, see examples below. Child classes * do not need to implement all of the abstract methods in the class. The child * only needs to implement the methods that are needed. * * @since 2.1.0 * * @package WordPress * @abstract */ #[AllowDynamicProperties] class Walker { /** * What the class handles. * * @since 2.1.0 * @var string */ public $tree_type; /** * DB fields to use. * * @since 2.1.0 * @var string[] */ public $db_fields; /** * Max number of pages walked by the paged walker. * * @since 2.7.0 * @var int */ public $max_pages = 1; /** * Whether the current element has children or not. * * To be used in start_el(). * * @since 4.0.0 * @var bool */ public $has_children; /** * Starts the list before the elements are added. * * The $args parameter holds additional values that may be used with the child * class methods. This method is called at the start of the output list. * * @since 2.1.0 * @abstract * * @param string $output Used to append additional content (passed by reference). * @param int $depth Depth of the item. * @param array $args An array of additional arguments. */ public function start_lvl( &$output, $depth = 0, $args = array() ) {} /** * Ends the list of after the elements are added. * * The $args parameter holds additional values that may be used with the child * class methods. This method finishes the list at the end of output of the elements. * * @since 2.1.0 * @abstract * * @param string $output Used to append additional content (passed by reference). * @param int $depth Depth of the item. * @param array $args An array of additional arguments. */ public function end_lvl( &$output, $depth = 0, $args = array() ) {} /** * Starts the element output. * * The $args parameter holds additional values that may be used with the child * class methods. Also includes the element output. * * @since 2.1.0 * @since 5.9.0 Renamed `$object` (a PHP reserved keyword) to `$data_object` for PHP 8 named parameter support. * @abstract * * @param string $output Used to append additional content (passed by reference). * @param object $data_object The data object. * @param int $depth Depth of the item. * @param array $args An array of additional arguments. * @param int $current_object_id Optional. ID of the current item. Default 0. */ public function start_el( &$output, $data_object, $depth = 0, $args = array(), $current_object_id = 0 ) {} /** * Ends the element output, if needed. * * The $args parameter holds additional values that may be used with the child class methods. * * @since 2.1.0 * @since 5.9.0 Renamed `$object` (a PHP reserved keyword) to `$data_object` for PHP 8 named parameter support. * @abstract * * @param string $output Used to append additional content (passed by reference). * @param object $data_object The data object. * @param int $depth Depth of the item. * @param array $args An array of additional arguments. */ public function end_el( &$output, $data_object, $depth = 0, $args = array() ) {} /** * Traverses elements to create list from elements. * * Display one element if the element doesn't have any children otherwise, * display the element and its children. Will only traverse up to the max * depth and no ignore elements under that depth. It is possible to set the * max depth to include all depths, see walk() method. * * This method should not be called directly, use the walk() method instead. * * @since 2.5.0 * * @param object $element Data object. * @param array $children_elements List of elements to continue traversing (passed by reference). * @param int $max_depth Max depth to traverse. * @param int $depth Depth of current element. * @param array $args An array of arguments. * @param string $output Used to append additional content (passed by reference). */ public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) { if ( ! $element ) { return; } $id_field = $this->db_fields['id']; $id = $element->$id_field; // Display this element. $this->has_children = ! empty( $children_elements[ $id ] ); if ( isset( $args[0] ) && is_array( $args[0] ) ) { $args[0]['has_children'] = $this->has_children; // Back-compat. } $this->start_el( $output, $element, $depth, ...array_values( $args ) ); // Descend only when the depth is right and there are children for this element. if ( ( 0 == $max_depth || $max_depth > $depth + 1 ) && isset( $children_elements[ $id ] ) ) { foreach ( $children_elements[ $id ] as $child ) { if ( ! isset( $newlevel ) ) { $newlevel = true; // Start the child delimiter. $this->start_lvl( $output, $depth, ...array_values( $args ) ); } $this->display_element( $child, $children_elements, $max_depth, $depth + 1, $args, $output ); } unset( $children_elements[ $id ] ); } if ( isset( $newlevel ) && $newlevel ) { // End the child delimiter. $this->end_lvl( $output, $depth, ...array_values( $args ) ); } // End this element. $this->end_el( $output, $element, $depth, ...array_values( $args ) ); } /** * Displays array of elements hierarchically. * * Does not assume any existing order of elements. * * $max_depth = -1 means flatly display every element. * $max_depth = 0 means display all levels. * $max_depth > 0 specifies the number of display levels. * * @since 2.1.0 * @since 5.3.0 Formalized the existing `...$args` parameter by adding it * to the function signature. * * @param array $elements An array of elements. * @param int $max_depth The maximum hierarchical depth. * @param mixed ...$args Optional additional arguments. * @return string The hierarchical item output. */ public function walk( $elements, $max_depth, ...$args ) { $output = ''; // Invalid parameter or nothing to walk. if ( $max_depth < -1 || empty( $elements ) ) { return $output; } $parent_field = $this->db_fields['parent']; // Flat display. if ( -1 == $max_depth ) { $empty_array = array(); foreach ( $elements as $e ) { $this->display_element( $e, $empty_array, 1, 0, $args, $output ); } return $output; } /* * Need to display in hierarchical order. * Separate elements into two buckets: top level and children elements. * Children_elements is two dimensional array. Example: * Children_elements[10][] contains all sub-elements whose parent is 10. */ $top_level_elements = array(); $children_elements = array(); foreach ( $elements as $e ) { if ( empty( $e->$parent_field ) ) { $top_level_elements[] = $e; } else { $children_elements[ $e->$parent_field ][] = $e; } } /* * When none of the elements is top level. * Assume the first one must be root of the sub elements. */ if ( empty( $top_level_elements ) ) { $first = array_slice( $elements, 0, 1 ); $root = $first[0]; $top_level_elements = array(); $children_elements = array(); foreach ( $elements as $e ) { if ( $root->$parent_field == $e->$parent_field ) { $top_level_elements[] = $e; } else { $children_elements[ $e->$parent_field ][] = $e; } } } foreach ( $top_level_elements as $e ) { $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output ); } /* * If we are displaying all levels, and remaining children_elements is not empty, * then we got orphans, which should be displayed regardless. */ if ( ( 0 == $max_depth ) && count( $children_elements ) > 0 ) { $empty_array = array(); foreach ( $children_elements as $orphans ) { foreach ( $orphans as $op ) { $this->display_element( $op, $empty_array, 1, 0, $args, $output ); } } } return $output; } /** * Produces a page of nested elements. * * Given an array of hierarchical elements, the maximum depth, a specific page number, * and number of elements per page, this function first determines all top level root elements * belonging to that page, then lists them and all of their children in hierarchical order. * * $max_depth = 0 means display all levels. * $max_depth > 0 specifies the number of display levels. * * @since 2.7.0 * @since 5.3.0 Formalized the existing `...$args` parameter by adding it * to the function signature. * * @param array $elements An array of elements. * @param int $max_depth The maximum hierarchical depth. * @param int $page_num The specific page number, beginning with 1. * @param int $per_page Number of elements per page. * @param mixed ...$args Optional additional arguments. * @return string XHTML of the specified page of elements. */ public function paged_walk( $elements, $max_depth, $page_num, $per_page, ...$args ) { if ( empty( $elements ) || $max_depth < -1 ) { return ''; } $output = ''; $parent_field = $this->db_fields['parent']; $count = -1; if ( -1 == $max_depth ) { $total_top = count( $elements ); } if ( $page_num < 1 || $per_page < 0 ) { // No paging. $paging = false; $start = 0; if ( -1 == $max_depth ) { $end = $total_top; } $this->max_pages = 1; } else { $paging = true; $start = ( (int) $page_num - 1 ) * (int) $per_page; $end = $start + $per_page; if ( -1 == $max_depth ) { $this->max_pages = (int) ceil( $total_top / $per_page ); } } // Flat display. if ( -1 == $max_depth ) { if ( ! empty( $args[0]['reverse_top_level'] ) ) { $elements = array_reverse( $elements ); $oldstart = $start; $start = $total_top - $end; $end = $total_top - $oldstart; } $empty_array = array(); foreach ( $elements as $e ) { ++$count; if ( $count < $start ) { continue; } if ( $count >= $end ) { break; } $this->display_element( $e, $empty_array, 1, 0, $args, $output ); } return $output; } /* * Separate elements into two buckets: top level and children elements. * Children_elements is two dimensional array, e.g. * $children_elements[10][] contains all sub-elements whose parent is 10. */ $top_level_elements = array(); $children_elements = array(); foreach ( $elements as $e ) { if ( empty( $e->$parent_field ) ) { $top_level_elements[] = $e; } else { $children_elements[ $e->$parent_field ][] = $e; } } $total_top = count( $top_level_elements ); if ( $paging ) { $this->max_pages = (int) ceil( $total_top / $per_page ); } else { $end = $total_top; } if ( ! empty( $args[0]['reverse_top_level'] ) ) { $top_level_elements = array_reverse( $top_level_elements ); $oldstart = $start; $start = $total_top - $end; $end = $total_top - $oldstart; } if ( ! empty( $args[0]['reverse_children'] ) ) { foreach ( $children_elements as $parent => $children ) { $children_elements[ $parent ] = array_reverse( $children ); } } foreach ( $top_level_elements as $e ) { ++$count; // For the last page, need to unset earlier children in order to keep track of orphans. if ( $end >= $total_top && $count < $start ) { $this->unset_children( $e, $children_elements ); } if ( $count < $start ) { continue; } if ( $count >= $end ) { break; } $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output ); } if ( $end >= $total_top && count( $children_elements ) > 0 ) { $empty_array = array(); foreach ( $children_elements as $orphans ) { foreach ( $orphans as $op ) { $this->display_element( $op, $empty_array, 1, 0, $args, $output ); } } } return $output; } /** * Calculates the total number of root elements. * * @since 2.7.0 * * @param array $elements Elements to list. * @return int Number of root elements. */ public function get_number_of_root_elements( $elements ) { $num = 0; $parent_field = $this->db_fields['parent']; foreach ( $elements as $e ) { if ( empty( $e->$parent_field ) ) { ++$num; } } return $num; } /** * Unsets all the children for a given top level element. * * @since 2.7.0 * * @param object $element The top level element. * @param array $children_elements The children elements. */ public function unset_children( $element, &$children_elements ) { if ( ! $element || ! $children_elements ) { return; } $id_field = $this->db_fields['id']; $id = $element->$id_field; if ( ! empty( $children_elements[ $id ] ) && is_array( $children_elements[ $id ] ) ) { foreach ( (array) $children_elements[ $id ] as $child ) { $this->unset_children( $child, $children_elements ); } } unset( $children_elements[ $id ] ); } } /** * Core User Role & Capabilities API * * @package WordPress * @subpackage Users */ /** * Maps a capability to the primitive capabilities required of the given user to * satisfy the capability being checked. * * This function also accepts an ID of an object to map against if the capability is a meta capability. Meta * capabilities such as `edit_post` and `edit_user` are capabilities used by this function to map to primitive * capabilities that a user or role requires, such as `edit_posts` and `edit_others_posts`. * * Example usage: * * map_meta_cap( 'edit_posts', $user->ID ); * map_meta_cap( 'edit_post', $user->ID, $post->ID ); * map_meta_cap( 'edit_post_meta', $user->ID, $post->ID, $meta_key ); * * This function does not check whether the user has the required capabilities, * it just returns what the required capabilities are. * * @since 2.0.0 * @since 4.9.6 Added the `export_others_personal_data`, `erase_others_personal_data`, * and `manage_privacy_options` capabilities. * @since 5.1.0 Added the `update_php` capability. * @since 5.2.0 Added the `resume_plugin` and `resume_theme` capabilities. * @since 5.3.0 Formalized the existing and already documented `...$args` parameter * by adding it to the function signature. * @since 5.7.0 Added the `create_app_password`, `list_app_passwords`, `read_app_password`, * `edit_app_password`, `delete_app_passwords`, `delete_app_password`, * and `update_https` capabilities. * * @global array $post_type_meta_caps Used to get post type meta capabilities. * * @param string $cap Capability being checked. * @param int $user_id User ID. * @param mixed ...$args Optional further parameters, typically starting with an object ID. * @return string[] Primitive capabilities required of the user. */ function map_meta_cap( $cap, $user_id, ...$args ) { $caps = array(); switch ( $cap ) { case 'remove_user': // In multisite the user must be a super admin to remove themselves. if ( isset( $args[0] ) && $user_id == $args[0] && ! is_super_admin( $user_id ) ) { $caps[] = 'do_not_allow'; } else { $caps[] = 'remove_users'; } break; case 'promote_user': case 'add_users': $caps[] = 'promote_users'; break; case 'edit_user': case 'edit_users': // Allow user to edit themselves. if ( 'edit_user' === $cap && isset( $args[0] ) && $user_id == $args[0] ) { break; } // In multisite the user must have manage_network_users caps. If editing a super admin, the user must be a super admin. if ( is_multisite() && ( ( ! is_super_admin( $user_id ) && 'edit_user' === $cap && is_super_admin( $args[0] ) ) || ! user_can( $user_id, 'manage_network_users' ) ) ) { $caps[] = 'do_not_allow'; } else { $caps[] = 'edit_users'; // edit_user maps to edit_users. } break; case 'delete_post': case 'delete_page': if ( ! isset( $args[0] ) ) { if ( 'delete_post' === $cap ) { /* translators: %s: Capability name. */ $message = __( 'When checking for the %s capability, you must always check it against a specific post.' ); } else { /* translators: %s: Capability name. */ $message = __( 'When checking for the %s capability, you must always check it against a specific page.' ); } _doing_it_wrong( __FUNCTION__, sprintf( $message, '' . $cap . '
' ),
'6.1.0'
);
$caps[] = 'do_not_allow';
break;
}
$post = get_post( $args[0] );
if ( ! $post ) {
$caps[] = 'do_not_allow';
break;
}
if ( 'revision' === $post->post_type ) {
$caps[] = 'do_not_allow';
break;
}
if ( ( get_option( 'page_for_posts' ) == $post->ID ) || ( get_option( 'page_on_front' ) == $post->ID ) ) {
$caps[] = 'manage_options';
break;
}
$post_type = get_post_type_object( $post->post_type );
if ( ! $post_type ) {
/* translators: 1: Post type, 2: Capability name. */
$message = __( 'The post type %1$s is not registered, so it may not be reliable to check the capability %2$s against a post of that type.' );
_doing_it_wrong(
__FUNCTION__,
sprintf(
$message,
'' . $post->post_type . '
',
'' . $cap . '
'
),
'4.4.0'
);
$caps[] = 'edit_others_posts';
break;
}
if ( ! $post_type->map_meta_cap ) {
$caps[] = $post_type->cap->$cap;
// Prior to 3.1 we would re-call map_meta_cap here.
if ( 'delete_post' === $cap ) {
$cap = $post_type->cap->$cap;
}
break;
}
// If the post author is set and the user is the author...
if ( $post->post_author && $user_id == $post->post_author ) {
// If the post is published or scheduled...
if ( in_array( $post->post_status, array( 'publish', 'future' ), true ) ) {
$caps[] = $post_type->cap->delete_published_posts;
} elseif ( 'trash' === $post->post_status ) {
$status = get_post_meta( $post->ID, '_wp_trash_meta_status', true );
if ( in_array( $status, array( 'publish', 'future' ), true ) ) {
$caps[] = $post_type->cap->delete_published_posts;
} else {
$caps[] = $post_type->cap->delete_posts;
}
} else {
// If the post is draft...
$caps[] = $post_type->cap->delete_posts;
}
} else {
// The user is trying to edit someone else's post.
$caps[] = $post_type->cap->delete_others_posts;
// The post is published or scheduled, extra cap required.
if ( in_array( $post->post_status, array( 'publish', 'future' ), true ) ) {
$caps[] = $post_type->cap->delete_published_posts;
} elseif ( 'private' === $post->post_status ) {
$caps[] = $post_type->cap->delete_private_posts;
}
}
/*
* Setting the privacy policy page requires `manage_privacy_options`,
* so deleting it should require that too.
*/
if ( (int) get_option( 'wp_page_for_privacy_policy' ) === $post->ID ) {
$caps = array_merge( $caps, map_meta_cap( 'manage_privacy_options', $user_id ) );
}
break;
/*
* edit_post breaks down to edit_posts, edit_published_posts, or
* edit_others_posts.
*/
case 'edit_post':
case 'edit_page':
if ( ! isset( $args[0] ) ) {
if ( 'edit_post' === $cap ) {
/* translators: %s: Capability name. */
$message = __( 'When checking for the %s capability, you must always check it against a specific post.' );
} else {
/* translators: %s: Capability name. */
$message = __( 'When checking for the %s capability, you must always check it against a specific page.' );
}
_doing_it_wrong(
__FUNCTION__,
sprintf( $message, '' . $cap . '
' ),
'6.1.0'
);
$caps[] = 'do_not_allow';
break;
}
$post = get_post( $args[0] );
if ( ! $post ) {
$caps[] = 'do_not_allow';
break;
}
if ( 'revision' === $post->post_type ) {
$post = get_post( $post->post_parent );
if ( ! $post ) {
$caps[] = 'do_not_allow';
break;
}
}
$post_type = get_post_type_object( $post->post_type );
if ( ! $post_type ) {
/* translators: 1: Post type, 2: Capability name. */
$message = __( 'The post type %1$s is not registered, so it may not be reliable to check the capability %2$s against a post of that type.' );
_doing_it_wrong(
__FUNCTION__,
sprintf(
$message,
'' . $post->post_type . '
',
'' . $cap . '
'
),
'4.4.0'
);
$caps[] = 'edit_others_posts';
break;
}
if ( ! $post_type->map_meta_cap ) {
$caps[] = $post_type->cap->$cap;
// Prior to 3.1 we would re-call map_meta_cap here.
if ( 'edit_post' === $cap ) {
$cap = $post_type->cap->$cap;
}
break;
}
// If the post author is set and the user is the author...
if ( $post->post_author && $user_id == $post->post_author ) {
// If the post is published or scheduled...
if ( in_array( $post->post_status, array( 'publish', 'future' ), true ) ) {
$caps[] = $post_type->cap->edit_published_posts;
} elseif ( 'trash' === $post->post_status ) {
$status = get_post_meta( $post->ID, '_wp_trash_meta_status', true );
if ( in_array( $status, array( 'publish', 'future' ), true ) ) {
$caps[] = $post_type->cap->edit_published_posts;
} else {
$caps[] = $post_type->cap->edit_posts;
}
} else {
// If the post is draft...
$caps[] = $post_type->cap->edit_posts;
}
} else {
// The user is trying to edit someone else's post.
$caps[] = $post_type->cap->edit_others_posts;
// The post is published or scheduled, extra cap required.
if ( in_array( $post->post_status, array( 'publish', 'future' ), true ) ) {
$caps[] = $post_type->cap->edit_published_posts;
} elseif ( 'private' === $post->post_status ) {
$caps[] = $post_type->cap->edit_private_posts;
}
}
/*
* Setting the privacy policy page requires `manage_privacy_options`,
* so editing it should require that too.
*/
if ( (int) get_option( 'wp_page_for_privacy_policy' ) === $post->ID ) {
$caps = array_merge( $caps, map_meta_cap( 'manage_privacy_options', $user_id ) );
}
break;
case 'read_post':
case 'read_page':
if ( ! isset( $args[0] ) ) {
if ( 'read_post' === $cap ) {
/* translators: %s: Capability name. */
$message = __( 'When checking for the %s capability, you must always check it against a specific post.' );
} else {
/* translators: %s: Capability name. */
$message = __( 'When checking for the %s capability, you must always check it against a specific page.' );
}
_doing_it_wrong(
__FUNCTION__,
sprintf( $message, '' . $cap . '
' ),
'6.1.0'
);
$caps[] = 'do_not_allow';
break;
}
$post = get_post( $args[0] );
if ( ! $post ) {
$caps[] = 'do_not_allow';
break;
}
if ( 'revision' === $post->post_type ) {
$post = get_post( $post->post_parent );
if ( ! $post ) {
$caps[] = 'do_not_allow';
break;
}
}
$post_type = get_post_type_object( $post->post_type );
if ( ! $post_type ) {
/* translators: 1: Post type, 2: Capability name. */
$message = __( 'The post type %1$s is not registered, so it may not be reliable to check the capability %2$s against a post of that type.' );
_doing_it_wrong(
__FUNCTION__,
sprintf(
$message,
'' . $post->post_type . '
',
'' . $cap . '
'
),
'4.4.0'
);
$caps[] = 'edit_others_posts';
break;
}
if ( ! $post_type->map_meta_cap ) {
$caps[] = $post_type->cap->$cap;
// Prior to 3.1 we would re-call map_meta_cap here.
if ( 'read_post' === $cap ) {
$cap = $post_type->cap->$cap;
}
break;
}
$status_obj = get_post_status_object( get_post_status( $post ) );
if ( ! $status_obj ) {
/* translators: 1: Post status, 2: Capability name. */
$message = __( 'The post status %1$s is not registered, so it may not be reliable to check the capability %2$s against a post with that status.' );
_doing_it_wrong(
__FUNCTION__,
sprintf(
$message,
'' . get_post_status( $post ) . '
',
'' . $cap . '
'
),
'5.4.0'
);
$caps[] = 'edit_others_posts';
break;
}
if ( $status_obj->public ) {
$caps[] = $post_type->cap->read;
break;
}
if ( $post->post_author && $user_id == $post->post_author ) {
$caps[] = $post_type->cap->read;
} elseif ( $status_obj->private ) {
$caps[] = $post_type->cap->read_private_posts;
} else {
$caps = map_meta_cap( 'edit_post', $user_id, $post->ID );
}
break;
case 'publish_post':
if ( ! isset( $args[0] ) ) {
/* translators: %s: Capability name. */
$message = __( 'When checking for the %s capability, you must always check it against a specific post.' );
_doing_it_wrong(
__FUNCTION__,
sprintf( $message, '' . $cap . '
' ),
'6.1.0'
);
$caps[] = 'do_not_allow';
break;
}
$post = get_post( $args[0] );
if ( ! $post ) {
$caps[] = 'do_not_allow';
break;
}
$post_type = get_post_type_object( $post->post_type );
if ( ! $post_type ) {
/* translators: 1: Post type, 2: Capability name. */
$message = __( 'The post type %1$s is not registered, so it may not be reliable to check the capability %2$s against a post of that type.' );
_doing_it_wrong(
__FUNCTION__,
sprintf(
$message,
'' . $post->post_type . '
',
'' . $cap . '
'
),
'4.4.0'
);
$caps[] = 'edit_others_posts';
break;
}
$caps[] = $post_type->cap->publish_posts;
break;
case 'edit_post_meta':
case 'delete_post_meta':
case 'add_post_meta':
case 'edit_comment_meta':
case 'delete_comment_meta':
case 'add_comment_meta':
case 'edit_term_meta':
case 'delete_term_meta':
case 'add_term_meta':
case 'edit_user_meta':
case 'delete_user_meta':
case 'add_user_meta':
$object_type = explode( '_', $cap )[1];
if ( ! isset( $args[0] ) ) {
if ( 'post' === $object_type ) {
/* translators: %s: Capability name. */
$message = __( 'When checking for the %s capability, you must always check it against a specific post.' );
} elseif ( 'comment' === $object_type ) {
/* translators: %s: Capability name. */
$message = __( 'When checking for the %s capability, you must always check it against a specific comment.' );
} elseif ( 'term' === $object_type ) {
/* translators: %s: Capability name. */
$message = __( 'When checking for the %s capability, you must always check it against a specific term.' );
} else {
/* translators: %s: Capability name. */
$message = __( 'When checking for the %s capability, you must always check it against a specific user.' );
}
_doing_it_wrong(
__FUNCTION__,
sprintf( $message, '' . $cap . '
' ),
'6.1.0'
);
$caps[] = 'do_not_allow';
break;
}
$object_id = (int) $args[0];
$object_subtype = get_object_subtype( $object_type, $object_id );
if ( empty( $object_subtype ) ) {
$caps[] = 'do_not_allow';
break;
}
$caps = map_meta_cap( "edit_{$object_type}", $user_id, $object_id );
$meta_key = isset( $args[1] ) ? $args[1] : false;
if ( $meta_key ) {
$allowed = ! is_protected_meta( $meta_key, $object_type );
if ( ! empty( $object_subtype ) && has_filter( "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}" ) ) {
/**
* Filters whether the user is allowed to edit a specific meta key of a specific object type and subtype.
*
* The dynamic portions of the hook name, `$object_type`, `$meta_key`,
* and `$object_subtype`, refer to the metadata object type (comment, post, term or user),
* the meta key value, and the object subtype respectively.
*
* @since 4.9.8
*
* @param bool $allowed Whether the user can add the object meta. Default false.
* @param string $meta_key The meta key.
* @param int $object_id Object ID.
* @param int $user_id User ID.
* @param string $cap Capability name.
* @param string[] $caps Array of the user's capabilities.
*/
$allowed = apply_filters( "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $allowed, $meta_key, $object_id, $user_id, $cap, $caps );
} else {
/**
* Filters whether the user is allowed to edit a specific meta key of a specific object type.
*
* Return true to have the mapped meta caps from `edit_{$object_type}` apply.
*
* The dynamic portion of the hook name, `$object_type` refers to the object type being filtered.
* The dynamic portion of the hook name, `$meta_key`, refers to the meta key passed to map_meta_cap().
*
* @since 3.3.0 As `auth_post_meta_{$meta_key}`.
* @since 4.6.0
*
* @param bool $allowed Whether the user can add the object meta. Default false.
* @param string $meta_key The meta key.
* @param int $object_id Object ID.
* @param int $user_id User ID.
* @param string $cap Capability name.
* @param string[] $caps Array of the user's capabilities.
*/
$allowed = apply_filters( "auth_{$object_type}_meta_{$meta_key}", $allowed, $meta_key, $object_id, $user_id, $cap, $caps );
}
if ( ! empty( $object_subtype ) ) {
/**
* Filters whether the user is allowed to edit meta for specific object types/subtypes.
*
* Return true to have the mapped meta caps from `edit_{$object_type}` apply.
*
* The dynamic portion of the hook name, `$object_type` refers to the object type being filtered.
* The dynamic portion of the hook name, `$object_subtype` refers to the object subtype being filtered.
* The dynamic portion of the hook name, `$meta_key`, refers to the meta key passed to map_meta_cap().
*
* @since 4.6.0 As `auth_post_{$post_type}_meta_{$meta_key}`.
* @since 4.7.0 Renamed from `auth_post_{$post_type}_meta_{$meta_key}` to
* `auth_{$object_type}_{$object_subtype}_meta_{$meta_key}`.
* @deprecated 4.9.8 Use {@see 'auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}'} instead.
*
* @param bool $allowed Whether the user can add the object meta. Default false.
* @param string $meta_key The meta key.
* @param int $object_id Object ID.
* @param int $user_id User ID.
* @param string $cap Capability name.
* @param string[] $caps Array of the user's capabilities.
*/
$allowed = apply_filters_deprecated(
"auth_{$object_type}_{$object_subtype}_meta_{$meta_key}",
array( $allowed, $meta_key, $object_id, $user_id, $cap, $caps ),
'4.9.8',
"auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}"
);
}
if ( ! $allowed ) {
$caps[] = $cap;
}
}
break;
case 'edit_comment':
if ( ! isset( $args[0] ) ) {
/* translators: %s: Capability name. */
$message = __( 'When checking for the %s capability, you must always check it against a specific comment.' );
_doing_it_wrong(
__FUNCTION__,
sprintf( $message, '' . $cap . '
' ),
'6.1.0'
);
$caps[] = 'do_not_allow';
break;
}
$comment = get_comment( $args[0] );
if ( ! $comment ) {
$caps[] = 'do_not_allow';
break;
}
$post = get_post( $comment->comment_post_ID );
/*
* If the post doesn't exist, we have an orphaned comment.
* Fall back to the edit_posts capability, instead.
*/
if ( $post ) {
$caps = map_meta_cap( 'edit_post', $user_id, $post->ID );
} else {
$caps = map_meta_cap( 'edit_posts', $user_id );
}
break;
case 'unfiltered_upload':
if ( defined( 'ALLOW_UNFILTERED_UPLOADS' ) && ALLOW_UNFILTERED_UPLOADS && ( ! is_multisite() || is_super_admin( $user_id ) ) ) {
$caps[] = $cap;
} else {
$caps[] = 'do_not_allow';
}
break;
case 'edit_css':
case 'unfiltered_html':
// Disallow unfiltered_html for all users, even admins and super admins.
if ( defined( 'DISALLOW_UNFILTERED_HTML' ) && DISALLOW_UNFILTERED_HTML ) {
$caps[] = 'do_not_allow';
} elseif ( is_multisite() && ! is_super_admin( $user_id ) ) {
$caps[] = 'do_not_allow';
} else {
$caps[] = 'unfiltered_html';
}
break;
case 'edit_files':
case 'edit_plugins':
case 'edit_themes':
// Disallow the file editors.
if ( defined( 'DISALLOW_FILE_EDIT' ) && DISALLOW_FILE_EDIT ) {
$caps[] = 'do_not_allow';
} elseif ( ! wp_is_file_mod_allowed( 'capability_edit_themes' ) ) {
$caps[] = 'do_not_allow';
} elseif ( is_multisite() && ! is_super_admin( $user_id ) ) {
$caps[] = 'do_not_allow';
} else {
$caps[] = $cap;
}
break;
case 'update_plugins':
case 'delete_plugins':
case 'install_plugins':
case 'upload_plugins':
case 'update_themes':
case 'delete_themes':
case 'install_themes':
case 'upload_themes':
case 'update_core':
/*
* Disallow anything that creates, deletes, or updates core, plugin, or theme files.
* Files in uploads are excepted.
*/
if ( ! wp_is_file_mod_allowed( 'capability_update_core' ) ) {
$caps[] = 'do_not_allow';
} elseif ( is_multisite() && ! is_super_admin( $user_id ) ) {
$caps[] = 'do_not_allow';
} elseif ( 'upload_themes' === $cap ) {
$caps[] = 'install_themes';
} elseif ( 'upload_plugins' === $cap ) {
$caps[] = 'install_plugins';
} else {
$caps[] = $cap;
}
break;
case 'install_languages':
case 'update_languages':
if ( ! wp_is_file_mod_allowed( 'can_install_language_pack' ) ) {
$caps[] = 'do_not_allow';
} elseif ( is_multisite() && ! is_super_admin( $user_id ) ) {
$caps[] = 'do_not_allow';
} else {
$caps[] = 'install_languages';
}
break;
case 'activate_plugins':
case 'deactivate_plugins':
case 'activate_plugin':
case 'deactivate_plugin':
$caps[] = 'activate_plugins';
if ( is_multisite() ) {
// update_, install_, and delete_ are handled above with is_super_admin().
$menu_perms = get_site_option( 'menu_items', array() );
if ( empty( $menu_perms['plugins'] ) ) {
$caps[] = 'manage_network_plugins';
}
}
break;
case 'resume_plugin':
$caps[] = 'resume_plugins';
break;
case 'resume_theme':
$caps[] = 'resume_themes';
break;
case 'delete_user':
case 'delete_users':
// If multisite only super admins can delete users.
if ( is_multisite() && ! is_super_admin( $user_id ) ) {
$caps[] = 'do_not_allow';
} else {
$caps[] = 'delete_users'; // delete_user maps to delete_users.
}
break;
case 'create_users':
if ( ! is_multisite() ) {
$caps[] = $cap;
} elseif ( is_super_admin( $user_id ) || get_site_option( 'add_new_users' ) ) {
$caps[] = $cap;
} else {
$caps[] = 'do_not_allow';
}
break;
case 'manage_links':
if ( get_option( 'link_manager_enabled' ) ) {
$caps[] = $cap;
} else {
$caps[] = 'do_not_allow';
}
break;
case 'customize':
$caps[] = 'edit_theme_options';
break;
case 'delete_site':
if ( is_multisite() ) {
$caps[] = 'manage_options';
} else {
$caps[] = 'do_not_allow';
}
break;
case 'edit_term':
case 'delete_term':
case 'assign_term':
if ( ! isset( $args[0] ) ) {
/* translators: %s: Capability name. */
$message = __( 'When checking for the %s capability, you must always check it against a specific term.' );
_doing_it_wrong(
__FUNCTION__,
sprintf( $message, '' . $cap . '
' ),
'6.1.0'
);
$caps[] = 'do_not_allow';
break;
}
$term_id = (int) $args[0];
$term = get_term( $term_id );
if ( ! $term || is_wp_error( $term ) ) {
$caps[] = 'do_not_allow';
break;
}
$tax = get_taxonomy( $term->taxonomy );
if ( ! $tax ) {
$caps[] = 'do_not_allow';
break;
}
if ( 'delete_term' === $cap
&& ( get_option( 'default_' . $term->taxonomy ) == $term->term_id
|| get_option( 'default_term_' . $term->taxonomy ) == $term->term_id )
) {
$caps[] = 'do_not_allow';
break;
}
$taxo_cap = $cap . 's';
$caps = map_meta_cap( $tax->cap->$taxo_cap, $user_id, $term_id );
break;
case 'manage_post_tags':
case 'edit_categories':
case 'edit_post_tags':
case 'delete_categories':
case 'delete_post_tags':
$caps[] = 'manage_categories';
break;
case 'assign_categories':
case 'assign_post_tags':
$caps[] = 'edit_posts';
break;
case 'create_sites':
case 'delete_sites':
case 'manage_network':
case 'manage_sites':
case 'manage_network_users':
case 'manage_network_plugins':
case 'manage_network_themes':
case 'manage_network_options':
case 'upgrade_network':
$caps[] = $cap;
break;
case 'setup_network':
if ( is_multisite() ) {
$caps[] = 'manage_network_options';
} else {
$caps[] = 'manage_options';
}
break;
case 'update_php':
if ( is_multisite() && ! is_super_admin( $user_id ) ) {
$caps[] = 'do_not_allow';
} else {
$caps[] = 'update_core';
}
break;
case 'update_https':
if ( is_multisite() && ! is_super_admin( $user_id ) ) {
$caps[] = 'do_not_allow';
} else {
$caps[] = 'manage_options';
$caps[] = 'update_core';
}
break;
case 'export_others_personal_data':
case 'erase_others_personal_data':
case 'manage_privacy_options':
$caps[] = is_multisite() ? 'manage_network' : 'manage_options';
break;
case 'create_app_password':
case 'list_app_passwords':
case 'read_app_password':
case 'edit_app_password':
case 'delete_app_passwords':
case 'delete_app_password':
$caps = map_meta_cap( 'edit_user', $user_id, $args[0] );
break;
default:
// Handle meta capabilities for custom post types.
global $post_type_meta_caps;
if ( isset( $post_type_meta_caps[ $cap ] ) ) {
return map_meta_cap( $post_type_meta_caps[ $cap ], $user_id, ...$args );
}
// Block capabilities map to their post equivalent.
$block_caps = array(
'edit_blocks',
'edit_others_blocks',
'publish_blocks',
'read_private_blocks',
'delete_blocks',
'delete_private_blocks',
'delete_published_blocks',
'delete_others_blocks',
'edit_private_blocks',
'edit_published_blocks',
);
if ( in_array( $cap, $block_caps, true ) ) {
$cap = str_replace( '_blocks', '_posts', $cap );
}
// If no meta caps match, return the original cap.
$caps[] = $cap;
}
/**
* Filters the primitive capabilities required of the given user to satisfy the
* capability being checked.
*
* @since 2.8.0
*
* @param string[] $caps Primitive capabilities required of the user.
* @param string $cap Capability being checked.
* @param int $user_id The user ID.
* @param array $args Adds context to the capability check, typically
* starting with an object ID.
*/
return apply_filters( 'map_meta_cap', $caps, $cap, $user_id, $args );
}
/**
* Returns whether the current user has the specified capability.
*
* This function also accepts an ID of an object to check against if the capability is a meta capability. Meta
* capabilities such as `edit_post` and `edit_user` are capabilities used by the `map_meta_cap()` function to
* map to primitive capabilities that a user or role has, such as `edit_posts` and `edit_others_posts`.
*
* Example usage:
*
* current_user_can( 'edit_posts' );
* current_user_can( 'edit_post', $post->ID );
* current_user_can( 'edit_post_meta', $post->ID, $meta_key );
*
* While checking against particular roles in place of a capability is supported
* in part, this practice is discouraged as it may produce unreliable results.
*
* Note: Will always return true if the current user is a super admin, unless specifically denied.
*
* @since 2.0.0
* @since 5.3.0 Formalized the existing and already documented `...$args` parameter
* by adding it to the function signature.
* @since 5.8.0 Converted to wrapper for the user_can() function.
*
* @see WP_User::has_cap()
* @see map_meta_cap()
*
* @param string $capability Capability name.
* @param mixed ...$args Optional further parameters, typically starting with an object ID.
* @return bool Whether the current user has the given capability. If `$capability` is a meta cap and `$object_id` is
* passed, whether the current user has the given meta capability for the given object.
*/
function current_user_can( $capability, ...$args ) {
return user_can( wp_get_current_user(), $capability, ...$args );
}
/**
* Returns whether the current user has the specified capability for a given site.
*
* This function also accepts an ID of an object to check against if the capability is a meta capability. Meta
* capabilities such as `edit_post` and `edit_user` are capabilities used by the `map_meta_cap()` function to
* map to primitive capabilities that a user or role has, such as `edit_posts` and `edit_others_posts`.
*
* Example usage:
*
* current_user_can_for_blog( $blog_id, 'edit_posts' );
* current_user_can_for_blog( $blog_id, 'edit_post', $post->ID );
* current_user_can_for_blog( $blog_id, 'edit_post_meta', $post->ID, $meta_key );
*
* @since 3.0.0
* @since 5.3.0 Formalized the existing and already documented `...$args` parameter
* by adding it to the function signature.
* @since 5.8.0 Wraps current_user_can() after switching to blog.
*
* @param int $blog_id Site ID.
* @param string $capability Capability name.
* @param mixed ...$args Optional further parameters, typically starting with an object ID.
* @return bool Whether the user has the given capability.
*/
function current_user_can_for_blog( $blog_id, $capability, ...$args ) {
$switched = is_multisite() ? switch_to_blog( $blog_id ) : false;
$can = current_user_can( $capability, ...$args );
if ( $switched ) {
restore_current_blog();
}
return $can;
}
/**
* Returns whether the author of the supplied post has the specified capability.
*
* This function also accepts an ID of an object to check against if the capability is a meta capability. Meta
* capabilities such as `edit_post` and `edit_user` are capabilities used by the `map_meta_cap()` function to
* map to primitive capabilities that a user or role has, such as `edit_posts` and `edit_others_posts`.
*
* Example usage:
*
* author_can( $post, 'edit_posts' );
* author_can( $post, 'edit_post', $post->ID );
* author_can( $post, 'edit_post_meta', $post->ID, $meta_key );
*
* @since 2.9.0
* @since 5.3.0 Formalized the existing and already documented `...$args` parameter
* by adding it to the function signature.
*
* @param int|WP_Post $post Post ID or post object.
* @param string $capability Capability name.
* @param mixed ...$args Optional further parameters, typically starting with an object ID.
* @return bool Whether the post author has the given capability.
*/
function author_can( $post, $capability, ...$args ) {
$post = get_post( $post );
if ( ! $post ) {
return false;
}
$author = get_userdata( $post->post_author );
if ( ! $author ) {
return false;
}
return $author->has_cap( $capability, ...$args );
}
/**
* Returns whether a particular user has the specified capability.
*
* This function also accepts an ID of an object to check against if the capability is a meta capability. Meta
* capabilities such as `edit_post` and `edit_user` are capabilities used by the `map_meta_cap()` function to
* map to primitive capabilities that a user or role has, such as `edit_posts` and `edit_others_posts`.
*
* Example usage:
*
* user_can( $user->ID, 'edit_posts' );
* user_can( $user->ID, 'edit_post', $post->ID );
* user_can( $user->ID, 'edit_post_meta', $post->ID, $meta_key );
*
* @since 3.1.0
* @since 5.3.0 Formalized the existing and already documented `...$args` parameter
* by adding it to the function signature.
*
* @param int|WP_User $user User ID or object.
* @param string $capability Capability name.
* @param mixed ...$args Optional further parameters, typically starting with an object ID.
* @return bool Whether the user has the given capability.
*/
function user_can( $user, $capability, ...$args ) {
if ( ! is_object( $user ) ) {
$user = get_userdata( $user );
}
if ( empty( $user ) ) {
// User is logged out, create anonymous user object.
$user = new WP_User( 0 );
$user->init( new stdClass() );
}
return $user->has_cap( $capability, ...$args );
}
/**
* Retrieves the global WP_Roles instance and instantiates it if necessary.
*
* @since 4.3.0
*
* @global WP_Roles $wp_roles WordPress role management object.
*
* @return WP_Roles WP_Roles global instance if not already instantiated.
*/
function wp_roles() {
global $wp_roles;
if ( ! isset( $wp_roles ) ) {
$wp_roles = new WP_Roles();
}
return $wp_roles;
}
/**
* Retrieves role object.
*
* @since 2.0.0
*
* @param string $role Role name.
* @return WP_Role|null WP_Role object if found, null if the role does not exist.
*/
function get_role( $role ) {
return wp_roles()->get_role( $role );
}
/**
* Adds a role, if it does not exist.
*
* @since 2.0.0
*
* @param string $role Role name.
* @param string $display_name Display name for role.
* @param bool[] $capabilities List of capabilities keyed by the capability name,
* e.g. array( 'edit_posts' => true, 'delete_posts' => false ).
* @return WP_Role|void WP_Role object, if the role is added.
*/
function add_role( $role, $display_name, $capabilities = array() ) {
if ( empty( $role ) ) {
return;
}
return wp_roles()->add_role( $role, $display_name, $capabilities );
}
/**
* Removes a role, if it exists.
*
* @since 2.0.0
*
* @param string $role Role name.
*/
function remove_role( $role ) {
wp_roles()->remove_role( $role );
}
/**
* Retrieves a list of super admins.
*
* @since 3.0.0
*
* @global array $super_admins
*
* @return string[] List of super admin logins.
*/
function get_super_admins() {
global $super_admins;
if ( isset( $super_admins ) ) {
return $super_admins;
} else {
return get_site_option( 'site_admins', array( 'admin' ) );
}
}
/**
* Determines whether user is a site admin.
*
* @since 3.0.0
*
* @param int|false $user_id Optional. The ID of a user. Defaults to false, to check the current user.
* @return bool Whether the user is a site admin.
*/
function is_super_admin( $user_id = false ) {
if ( ! $user_id ) {
$user = wp_get_current_user();
} else {
$user = get_userdata( $user_id );
}
if ( ! $user || ! $user->exists() ) {
return false;
}
if ( is_multisite() ) {
$super_admins = get_super_admins();
if ( is_array( $super_admins ) && in_array( $user->user_login, $super_admins, true ) ) {
return true;
}
} else {
if ( $user->has_cap( 'delete_users' ) ) {
return true;
}
}
return false;
}
/**
* Grants Super Admin privileges.
*
* @since 3.0.0
*
* @global array $super_admins
*
* @param int $user_id ID of the user to be granted Super Admin privileges.
* @return bool True on success, false on failure. This can fail when the user is
* already a super admin or when the `$super_admins` global is defined.
*/
function grant_super_admin( $user_id ) {
// If global super_admins override is defined, there is nothing to do here.
if ( isset( $GLOBALS['super_admins'] ) || ! is_multisite() ) {
return false;
}
/**
* Fires before the user is granted Super Admin privileges.
*
* @since 3.0.0
*
* @param int $user_id ID of the user that is about to be granted Super Admin privileges.
*/
do_action( 'grant_super_admin', $user_id );
// Directly fetch site_admins instead of using get_super_admins().
$super_admins = get_site_option( 'site_admins', array( 'admin' ) );
$user = get_userdata( $user_id );
if ( $user && ! in_array( $user->user_login, $super_admins, true ) ) {
$super_admins[] = $user->user_login;
update_site_option( 'site_admins', $super_admins );
/**
* Fires after the user is granted Super Admin privileges.
*
* @since 3.0.0
*
* @param int $user_id ID of the user that was granted Super Admin privileges.
*/
do_action( 'granted_super_admin', $user_id );
return true;
}
return false;
}
/**
* Revokes Super Admin privileges.
*
* @since 3.0.0
*
* @global array $super_admins
*
* @param int $user_id ID of the user Super Admin privileges to be revoked from.
* @return bool True on success, false on failure. This can fail when the user's email
* is the network admin email or when the `$super_admins` global is defined.
*/
function revoke_super_admin( $user_id ) {
// If global super_admins override is defined, there is nothing to do here.
if ( isset( $GLOBALS['super_admins'] ) || ! is_multisite() ) {
return false;
}
/**
* Fires before the user's Super Admin privileges are revoked.
*
* @since 3.0.0
*
* @param int $user_id ID of the user Super Admin privileges are being revoked from.
*/
do_action( 'revoke_super_admin', $user_id );
// Directly fetch site_admins instead of using get_super_admins().
$super_admins = get_site_option( 'site_admins', array( 'admin' ) );
$user = get_userdata( $user_id );
if ( $user && 0 !== strcasecmp( $user->user_email, get_site_option( 'admin_email' ) ) ) {
$key = array_search( $user->user_login, $super_admins, true );
if ( false !== $key ) {
unset( $super_admins[ $key ] );
update_site_option( 'site_admins', $super_admins );
/**
* Fires after the user's Super Admin privileges are revoked.
*
* @since 3.0.0
*
* @param int $user_id ID of the user Super Admin privileges were revoked from.
*/
do_action( 'revoked_super_admin', $user_id );
return true;
}
}
return false;
}
/**
* Filters the user capabilities to grant the 'install_languages' capability as necessary.
*
* A user must have at least one out of the 'update_core', 'install_plugins', and
* 'install_themes' capabilities to qualify for 'install_languages'.
*
* @since 4.9.0
*
* @param bool[] $allcaps An array of all the user's capabilities.
* @return bool[] Filtered array of the user's capabilities.
*/
function wp_maybe_grant_install_languages_cap( $allcaps ) {
if ( ! empty( $allcaps['update_core'] ) || ! empty( $allcaps['install_plugins'] ) || ! empty( $allcaps['install_themes'] ) ) {
$allcaps['install_languages'] = true;
}
return $allcaps;
}
/**
* Filters the user capabilities to grant the 'resume_plugins' and 'resume_themes' capabilities as necessary.
*
* @since 5.2.0
*
* @param bool[] $allcaps An array of all the user's capabilities.
* @return bool[] Filtered array of the user's capabilities.
*/
function wp_maybe_grant_resume_extensions_caps( $allcaps ) {
// Even in a multisite, regular administrators should be able to resume plugins.
if ( ! empty( $allcaps['activate_plugins'] ) ) {
$allcaps['resume_plugins'] = true;
}
// Even in a multisite, regular administrators should be able to resume themes.
if ( ! empty( $allcaps['switch_themes'] ) ) {
$allcaps['resume_themes'] = true;
}
return $allcaps;
}
/**
* Filters the user capabilities to grant the 'view_site_health_checks' capabilities as necessary.
*
* @since 5.2.2
*
* @param bool[] $allcaps An array of all the user's capabilities.
* @param string[] $caps Required primitive capabilities for the requested capability.
* @param array $args {
* Arguments that accompany the requested capability check.
*
* @type string $0 Requested capability.
* @type int $1 Concerned user ID.
* @type mixed ...$2 Optional second and further parameters, typically object ID.
* }
* @param WP_User $user The user object.
* @return bool[] Filtered array of the user's capabilities.
*/
function wp_maybe_grant_site_health_caps( $allcaps, $caps, $args, $user ) {
if ( ! empty( $allcaps['install_plugins'] ) && ( ! is_multisite() || is_super_admin( $user->ID ) ) ) {
$allcaps['view_site_health_checks'] = true;
}
return $allcaps;
}
return;
// Dummy gettext calls to get strings in the catalog.
/* translators: User role for administrators. */
_x( 'Administrator', 'User role' );
/* translators: User role for editors. */
_x( 'Editor', 'User role' );
/* translators: User role for authors. */
_x( 'Author', 'User role' );
/* translators: User role for contributors. */
_x( 'Contributor', 'User role' );
/* translators: User role for subscribers. */
_x( 'Subscriber', 'User role' );
/**
* User API: WP_User class
*
* @package WordPress
* @subpackage Users
* @since 4.4.0
*/
/**
* Core class used to implement the WP_User object.
*
* @since 2.0.0
*
* @property string $nickname
* @property string $description
* @property string $user_description
* @property string $first_name
* @property string $user_firstname
* @property string $last_name
* @property string $user_lastname
* @property string $user_login
* @property string $user_pass
* @property string $user_nicename
* @property string $user_email
* @property string $user_url
* @property string $user_registered
* @property string $user_activation_key
* @property string $user_status
* @property int $user_level
* @property string $display_name
* @property string $spam
* @property string $deleted
* @property string $locale
* @property string $rich_editing
* @property string $syntax_highlighting
* @property string $use_ssl
*/
#[AllowDynamicProperties]
class WP_User {
/**
* User data container.
*
* @since 2.0.0
* @var stdClass
*/
public $data;
/**
* The user's ID.
*
* @since 2.1.0
* @var int
*/
public $ID = 0;
/**
* Capabilities that the individual user has been granted outside of those inherited from their role.
*
* @since 2.0.0
* @var bool[] Array of key/value pairs where keys represent a capability name
* and boolean values represent whether the user has that capability.
*/
public $caps = array();
/**
* User metadata option name.
*
* @since 2.0.0
* @var string
*/
public $cap_key;
/**
* The roles the user is part of.
*
* @since 2.0.0
* @var string[]
*/
public $roles = array();
/**
* All capabilities the user has, including individual and role based.
*
* @since 2.0.0
* @var bool[] Array of key/value pairs where keys represent a capability name
* and boolean values represent whether the user has that capability.
*/
public $allcaps = array();
/**
* The filter context applied to user data fields.
*
* @since 2.9.0
* @var string
*/
public $filter = null;
/**
* The site ID the capabilities of this user are initialized for.
*
* @since 4.9.0
* @var int
*/
private $site_id = 0;
/**
* @since 3.3.0
* @var array
*/
private static $back_compat_keys;
/**
* Constructor.
*
* Retrieves the userdata and passes it to WP_User::init().
*
* @since 2.0.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param int|string|stdClass|WP_User $id User's ID, a WP_User object, or a user object from the DB.
* @param string $name Optional. User's username
* @param int $site_id Optional Site ID, defaults to current site.
*/
public function __construct( $id = 0, $name = '', $site_id = '' ) {
global $wpdb;
if ( ! isset( self::$back_compat_keys ) ) {
$prefix = $wpdb->prefix;
self::$back_compat_keys = array(
'user_firstname' => 'first_name',
'user_lastname' => 'last_name',
'user_description' => 'description',
'user_level' => $prefix . 'user_level',
$prefix . 'usersettings' => $prefix . 'user-settings',
$prefix . 'usersettingstime' => $prefix . 'user-settings-time',
);
}
if ( $id instanceof WP_User ) {
$this->init( $id->data, $site_id );
return;
} elseif ( is_object( $id ) ) {
$this->init( $id, $site_id );
return;
}
if ( ! empty( $id ) && ! is_numeric( $id ) ) {
$name = $id;
$id = 0;
}
if ( $id ) {
$data = self::get_data_by( 'id', $id );
} else {
$data = self::get_data_by( 'login', $name );
}
if ( $data ) {
$this->init( $data, $site_id );
} else {
$this->data = new stdClass();
}
}
/**
* Sets up object properties, including capabilities.
*
* @since 3.3.0
*
* @param object $data User DB row object.
* @param int $site_id Optional. The site ID to initialize for.
*/
public function init( $data, $site_id = '' ) {
if ( ! isset( $data->ID ) ) {
$data->ID = 0;
}
$this->data = $data;
$this->ID = (int) $data->ID;
$this->for_site( $site_id );
}
/**
* Returns only the main user fields.
*
* @since 3.3.0
* @since 4.4.0 Added 'ID' as an alias of 'id' for the `$field` parameter.
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param string $field The field to query against: Accepts 'id', 'ID', 'slug', 'email' or 'login'.
* @param string|int $value The field value.
* @return object|false Raw user object.
*/
public static function get_data_by( $field, $value ) {
global $wpdb;
// 'ID' is an alias of 'id'.
if ( 'ID' === $field ) {
$field = 'id';
}
if ( 'id' === $field ) {
// Make sure the value is numeric to avoid casting objects, for example, to int 1.
if ( ! is_numeric( $value ) ) {
return false;
}
$value = (int) $value;
if ( $value < 1 ) {
return false;
}
} else {
$value = trim( $value );
}
if ( ! $value ) {
return false;
}
switch ( $field ) {
case 'id':
$user_id = $value;
$db_field = 'ID';
break;
case 'slug':
$user_id = wp_cache_get( $value, 'userslugs' );
$db_field = 'user_nicename';
break;
case 'email':
$user_id = wp_cache_get( $value, 'useremail' );
$db_field = 'user_email';
break;
case 'login':
$value = sanitize_user( $value );
$user_id = wp_cache_get( $value, 'userlogins' );
$db_field = 'user_login';
break;
default:
return false;
}
if ( false !== $user_id ) {
$user = wp_cache_get( $user_id, 'users' );
if ( $user ) {
return $user;
}
}
$user = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM $wpdb->users WHERE $db_field = %s LIMIT 1",
$value
)
);
if ( ! $user ) {
return false;
}
update_user_caches( $user );
return $user;
}
/**
* Magic method for checking the existence of a certain custom field.
*
* @since 3.3.0
*
* @param string $key User meta key to check if set.
* @return bool Whether the given user meta key is set.
*/
public function __isset( $key ) {
if ( 'id' === $key ) {
_deprecated_argument(
'WP_User->id',
'2.1.0',
sprintf(
/* translators: %s: WP_User->ID */
__( 'Use %s instead.' ),
'WP_User->ID
'
)
);
$key = 'ID';
}
if ( isset( $this->data->$key ) ) {
return true;
}
if ( isset( self::$back_compat_keys[ $key ] ) ) {
$key = self::$back_compat_keys[ $key ];
}
return metadata_exists( 'user', $this->ID, $key );
}
/**
* Magic method for accessing custom fields.
*
* @since 3.3.0
*
* @param string $key User meta key to retrieve.
* @return mixed Value of the given user meta key (if set). If `$key` is 'id', the user ID.
*/
public function __get( $key ) {
if ( 'id' === $key ) {
_deprecated_argument(
'WP_User->id',
'2.1.0',
sprintf(
/* translators: %s: WP_User->ID */
__( 'Use %s instead.' ),
'WP_User->ID
'
)
);
return $this->ID;
}
if ( isset( $this->data->$key ) ) {
$value = $this->data->$key;
} else {
if ( isset( self::$back_compat_keys[ $key ] ) ) {
$key = self::$back_compat_keys[ $key ];
}
$value = get_user_meta( $this->ID, $key, true );
}
if ( $this->filter ) {
$value = sanitize_user_field( $key, $value, $this->ID, $this->filter );
}
return $value;
}
/**
* Magic method for setting custom user fields.
*
* This method does not update custom fields in the database. It only stores
* the value on the WP_User instance.
*
* @since 3.3.0
*
* @param string $key User meta key.
* @param mixed $value User meta value.
*/
public function __set( $key, $value ) {
if ( 'id' === $key ) {
_deprecated_argument(
'WP_User->id',
'2.1.0',
sprintf(
/* translators: %s: WP_User->ID */
__( 'Use %s instead.' ),
'WP_User->ID
'
)
);
$this->ID = $value;
return;
}
$this->data->$key = $value;
}
/**
* Magic method for unsetting a certain custom field.
*
* @since 4.4.0
*
* @param string $key User meta key to unset.
*/
public function __unset( $key ) {
if ( 'id' === $key ) {
_deprecated_argument(
'WP_User->id',
'2.1.0',
sprintf(
/* translators: %s: WP_User->ID */
__( 'Use %s instead.' ),
'WP_User->ID
'
)
);
}
if ( isset( $this->data->$key ) ) {
unset( $this->data->$key );
}
if ( isset( self::$back_compat_keys[ $key ] ) ) {
unset( self::$back_compat_keys[ $key ] );
}
}
/**
* Determines whether the user exists in the database.
*
* @since 3.4.0
*
* @return bool True if user exists in the database, false if not.
*/
public function exists() {
return ! empty( $this->ID );
}
/**
* Retrieves the value of a property or meta key.
*
* Retrieves from the users and usermeta table.
*
* @since 3.3.0
*
* @param string $key Property
* @return mixed
*/
public function get( $key ) {
return $this->__get( $key );
}
/**
* Determines whether a property or meta key is set.
*
* Consults the users and usermeta tables.
*
* @since 3.3.0
*
* @param string $key Property.
* @return bool
*/
public function has_prop( $key ) {
return $this->__isset( $key );
}
/**
* Returns an array representation.
*
* @since 3.5.0
*
* @return array Array representation.
*/
public function to_array() {
return get_object_vars( $this->data );
}
/**
* Makes private/protected methods readable for backward compatibility.
*
* @since 4.3.0
*
* @param string $name Method to call.
* @param array $arguments Arguments to pass when calling.
* @return mixed|false Return value of the callback, false otherwise.
*/
public function __call( $name, $arguments ) {
if ( '_init_caps' === $name ) {
return $this->_init_caps( ...$arguments );
}
return false;
}
/**
* Sets up capability object properties.
*
* Will set the value for the 'cap_key' property to current database table
* prefix, followed by 'capabilities'. Will then check to see if the
* property matching the 'cap_key' exists and is an array. If so, it will be
* used.
*
* @since 2.1.0
* @deprecated 4.9.0 Use WP_User::for_site()
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param string $cap_key Optional capability key
*/
protected function _init_caps( $cap_key = '' ) {
global $wpdb;
_deprecated_function( __METHOD__, '4.9.0', 'WP_User::for_site()' );
if ( empty( $cap_key ) ) {
$this->cap_key = $wpdb->get_blog_prefix( $this->site_id ) . 'capabilities';
} else {
$this->cap_key = $cap_key;
}
$this->caps = $this->get_caps_data();
$this->get_role_caps();
}
/**
* Retrieves all of the capabilities of the user's roles, and merges them with
* individual user capabilities.
*
* All of the capabilities of the user's roles are merged with the user's individual
* capabilities. This means that the user can be denied specific capabilities that
* their role might have, but the user is specifically denied.
*
* @since 2.0.0
*
* @return bool[] Array of key/value pairs where keys represent a capability name
* and boolean values represent whether the user has that capability.
*/
public function get_role_caps() {
$switch_site = false;
if ( is_multisite() && get_current_blog_id() !== $this->site_id ) {
$switch_site = true;
switch_to_blog( $this->site_id );
}
$wp_roles = wp_roles();
// Filter out caps that are not role names and assign to $this->roles.
if ( is_array( $this->caps ) ) {
$this->roles = array_filter( array_keys( $this->caps ), array( $wp_roles, 'is_role' ) );
}
// Build $allcaps from role caps, overlay user's $caps.
$this->allcaps = array();
foreach ( (array) $this->roles as $role ) {
$the_role = $wp_roles->get_role( $role );
$this->allcaps = array_merge( (array) $this->allcaps, (array) $the_role->capabilities );
}
$this->allcaps = array_merge( (array) $this->allcaps, (array) $this->caps );
if ( $switch_site ) {
restore_current_blog();
}
return $this->allcaps;
}
/**
* Adds role to user.
*
* Updates the user's meta data option with capabilities and roles.
*
* @since 2.0.0
*
* @param string $role Role name.
*/
public function add_role( $role ) {
if ( empty( $role ) ) {
return;
}
if ( in_array( $role, $this->roles, true ) ) {
return;
}
$this->caps[ $role ] = true;
update_user_meta( $this->ID, $this->cap_key, $this->caps );
$this->get_role_caps();
$this->update_user_level_from_caps();
/**
* Fires immediately after the user has been given a new role.
*
* @since 4.3.0
*
* @param int $user_id The user ID.
* @param string $role The new role.
*/
do_action( 'add_user_role', $this->ID, $role );
}
/**
* Removes role from user.
*
* @since 2.0.0
*
* @param string $role Role name.
*/
public function remove_role( $role ) {
if ( ! in_array( $role, $this->roles, true ) ) {
return;
}
unset( $this->caps[ $role ] );
update_user_meta( $this->ID, $this->cap_key, $this->caps );
$this->get_role_caps();
$this->update_user_level_from_caps();
/**
* Fires immediately after a role as been removed from a user.
*
* @since 4.3.0
*
* @param int $user_id The user ID.
* @param string $role The removed role.
*/
do_action( 'remove_user_role', $this->ID, $role );
}
/**
* Sets the role of the user.
*
* This will remove the previous roles of the user and assign the user the
* new one. You can set the role to an empty string and it will remove all
* of the roles from the user.
*
* @since 2.0.0
*
* @param string $role Role name.
*/
public function set_role( $role ) {
if ( 1 === count( $this->roles ) && current( $this->roles ) === $role ) {
return;
}
foreach ( (array) $this->roles as $oldrole ) {
unset( $this->caps[ $oldrole ] );
}
$old_roles = $this->roles;
if ( ! empty( $role ) ) {
$this->caps[ $role ] = true;
$this->roles = array( $role => true );
} else {
$this->roles = array();
}
update_user_meta( $this->ID, $this->cap_key, $this->caps );
$this->get_role_caps();
$this->update_user_level_from_caps();
foreach ( $old_roles as $old_role ) {
if ( ! $old_role || $old_role === $role ) {
continue;
}
/** This action is documented in wp-includes/class-wp-user.php */
do_action( 'remove_user_role', $this->ID, $old_role );
}
if ( $role && ! in_array( $role, $old_roles, true ) ) {
/** This action is documented in wp-includes/class-wp-user.php */
do_action( 'add_user_role', $this->ID, $role );
}
/**
* Fires after the user's role has changed.
*
* @since 2.9.0
* @since 3.6.0 Added $old_roles to include an array of the user's previous roles.
*
* @param int $user_id The user ID.
* @param string $role The new role.
* @param string[] $old_roles An array of the user's previous roles.
*/
do_action( 'set_user_role', $this->ID, $role, $old_roles );
}
/**
* Chooses the maximum level the user has.
*
* Will compare the level from the $item parameter against the $max
* parameter. If the item is incorrect, then just the $max parameter value
* will be returned.
*
* Used to get the max level based on the capabilities the user has. This
* is also based on roles, so if the user is assigned the Administrator role
* then the capability 'level_10' will exist and the user will get that
* value.
*
* @since 2.0.0
*
* @param int $max Max level of user.
* @param string $item Level capability name.
* @return int Max Level.
*/
public function level_reduction( $max, $item ) {
if ( preg_match( '/^level_(10|[0-9])$/i', $item, $matches ) ) {
$level = (int) $matches[1];
return max( $max, $level );
} else {
return $max;
}
}
/**
* Updates the maximum user level for the user.
*
* Updates the 'user_level' user metadata (includes prefix that is the
* database table prefix) with the maximum user level. Gets the value from
* the all of the capabilities that the user has.
*
* @since 2.0.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*/
public function update_user_level_from_caps() {
global $wpdb;
$this->user_level = array_reduce( array_keys( $this->allcaps ), array( $this, 'level_reduction' ), 0 );
update_user_meta( $this->ID, $wpdb->get_blog_prefix() . 'user_level', $this->user_level );
}
/**
* Adds capability and grant or deny access to capability.
*
* @since 2.0.0
*
* @param string $cap Capability name.
* @param bool $grant Whether to grant capability to user.
*/
public function add_cap( $cap, $grant = true ) {
$this->caps[ $cap ] = $grant;
update_user_meta( $this->ID, $this->cap_key, $this->caps );
$this->get_role_caps();
$this->update_user_level_from_caps();
}
/**
* Removes capability from user.
*
* @since 2.0.0
*
* @param string $cap Capability name.
*/
public function remove_cap( $cap ) {
if ( ! isset( $this->caps[ $cap ] ) ) {
return;
}
unset( $this->caps[ $cap ] );
update_user_meta( $this->ID, $this->cap_key, $this->caps );
$this->get_role_caps();
$this->update_user_level_from_caps();
}
/**
* Removes all of the capabilities of the user.
*
* @since 2.1.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*/
public function remove_all_caps() {
global $wpdb;
$this->caps = array();
delete_user_meta( $this->ID, $this->cap_key );
delete_user_meta( $this->ID, $wpdb->get_blog_prefix() . 'user_level' );
$this->get_role_caps();
}
/**
* Returns whether the user has the specified capability.
*
* This function also accepts an ID of an object to check against if the capability is a meta capability. Meta
* capabilities such as `edit_post` and `edit_user` are capabilities used by the `map_meta_cap()` function to
* map to primitive capabilities that a user or role has, such as `edit_posts` and `edit_others_posts`.
*
* Example usage:
*
* $user->has_cap( 'edit_posts' );
* $user->has_cap( 'edit_post', $post->ID );
* $user->has_cap( 'edit_post_meta', $post->ID, $meta_key );
*
* While checking against a role in place of a capability is supported in part, this practice is discouraged as it
* may produce unreliable results.
*
* @since 2.0.0
* @since 5.3.0 Formalized the existing and already documented `...$args` parameter
* by adding it to the function signature.
*
* @see map_meta_cap()
*
* @param string $cap Capability name.
* @param mixed ...$args Optional further parameters, typically starting with an object ID.
* @return bool Whether the user has the given capability, or, if an object ID is passed, whether the user has
* the given capability for that object.
*/
public function has_cap( $cap, ...$args ) {
if ( is_numeric( $cap ) ) {
_deprecated_argument( __FUNCTION__, '2.0.0', __( 'Usage of user levels is deprecated. Use capabilities instead.' ) );
$cap = $this->translate_level_to_cap( $cap );
}
$caps = map_meta_cap( $cap, $this->ID, ...$args );
// Multisite super admin has all caps by definition, Unless specifically denied.
if ( is_multisite() && is_super_admin( $this->ID ) ) {
if ( in_array( 'do_not_allow', $caps, true ) ) {
return false;
}
return true;
}
// Maintain BC for the argument passed to the "user_has_cap" filter.
$args = array_merge( array( $cap, $this->ID ), $args );
/**
* Dynamically filter a user's capabilities.
*
* @since 2.0.0
* @since 3.7.0 Added the `$user` parameter.
*
* @param bool[] $allcaps Array of key/value pairs where keys represent a capability name
* and boolean values represent whether the user has that capability.
* @param string[] $caps Required primitive capabilities for the requested capability.
* @param array $args {
* Arguments that accompany the requested capability check.
*
* @type string $0 Requested capability.
* @type int $1 Concerned user ID.
* @type mixed ...$2 Optional second and further parameters, typically object ID.
* }
* @param WP_User $user The user object.
*/
$capabilities = apply_filters( 'user_has_cap', $this->allcaps, $caps, $args, $this );
// Everyone is allowed to exist.
$capabilities['exist'] = true;
// Nobody is allowed to do things they are not allowed to do.
unset( $capabilities['do_not_allow'] );
// Must have ALL requested caps.
foreach ( (array) $caps as $cap ) {
if ( empty( $capabilities[ $cap ] ) ) {
return false;
}
}
return true;
}
/**
* Converts numeric level to level capability name.
*
* Prepends 'level_' to level number.
*
* @since 2.0.0
*
* @param int $level Level number, 1 to 10.
* @return string
*/
public function translate_level_to_cap( $level ) {
return 'level_' . $level;
}
/**
* Sets the site to operate on. Defaults to the current site.
*
* @since 3.0.0
* @deprecated 4.9.0 Use WP_User::for_site()
*
* @param int $blog_id Optional. Site ID, defaults to current site.
*/
public function for_blog( $blog_id = '' ) {
_deprecated_function( __METHOD__, '4.9.0', 'WP_User::for_site()' );
$this->for_site( $blog_id );
}
/**
* Sets the site to operate on. Defaults to the current site.
*
* @since 4.9.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param int $site_id Site ID to initialize user capabilities for. Default is the current site.
*/
public function for_site( $site_id = '' ) {
global $wpdb;
if ( ! empty( $site_id ) ) {
$this->site_id = absint( $site_id );
} else {
$this->site_id = get_current_blog_id();
}
$this->cap_key = $wpdb->get_blog_prefix( $this->site_id ) . 'capabilities';
$this->caps = $this->get_caps_data();
$this->get_role_caps();
}
/**
* Gets the ID of the site for which the user's capabilities are currently initialized.
*
* @since 4.9.0
*
* @return int Site ID.
*/
public function get_site_id() {
return $this->site_id;
}
/**
* Gets the available user capabilities data.
*
* @since 4.9.0
*
* @return bool[] List of capabilities keyed by the capability name,
* e.g. `array( 'edit_posts' => true, 'delete_posts' => false )`.
*/
private function get_caps_data() {
$caps = get_user_meta( $this->ID, $this->cap_key, true );
if ( ! is_array( $caps ) ) {
return array();
}
return $caps;
}
}
/**
* WordPress Query API
*
* The query API attempts to get which part of WordPress the user is on. It
* also provides functionality for getting URL query information.
*
* @link https://developer.wordpress.org/themes/basics/the-loop/ More information on The Loop.
*
* @package WordPress
* @subpackage Query
*/
/**
* Retrieves the value of a query variable in the WP_Query class.
*
* @since 1.5.0
* @since 3.9.0 The `$default_value` argument was introduced.
*
* @global WP_Query $wp_query WordPress Query object.
*
* @param string $query_var The variable key to retrieve.
* @param mixed $default_value Optional. Value to return if the query variable is not set.
* Default empty string.
* @return mixed Contents of the query variable.
*/
function get_query_var( $query_var, $default_value = '' ) {
global $wp_query;
return $wp_query->get( $query_var, $default_value );
}
/**
* Retrieves the currently queried object.
*
* Wrapper for WP_Query::get_queried_object().
*
* @since 3.1.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return WP_Term|WP_Post_Type|WP_Post|WP_User|null The queried object.
*/
function get_queried_object() {
global $wp_query;
return $wp_query->get_queried_object();
}
/**
* Retrieves the ID of the currently queried object.
*
* Wrapper for WP_Query::get_queried_object_id().
*
* @since 3.1.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return int ID of the queried object.
*/
function get_queried_object_id() {
global $wp_query;
return $wp_query->get_queried_object_id();
}
/**
* Sets the value of a query variable in the WP_Query class.
*
* @since 2.2.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @param string $query_var Query variable key.
* @param mixed $value Query variable value.
*/
function set_query_var( $query_var, $value ) {
global $wp_query;
$wp_query->set( $query_var, $value );
}
/**
* Sets up The Loop with query parameters.
*
* Note: This function will completely override the main query and isn't intended for use
* by plugins or themes. Its overly-simplistic approach to modifying the main query can be
* problematic and should be avoided wherever possible. In most cases, there are better,
* more performant options for modifying the main query such as via the {@see 'pre_get_posts'}
* action within WP_Query.
*
* This must not be used within the WordPress Loop.
*
* @since 1.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @param array|string $query Array or string of WP_Query arguments.
* @return WP_Post[]|int[] Array of post objects or post IDs.
*/
function query_posts( $query ) {
$GLOBALS['wp_query'] = new WP_Query();
return $GLOBALS['wp_query']->query( $query );
}
/**
* Destroys the previous query and sets up a new query.
*
* This should be used after query_posts() and before another query_posts().
* This will remove obscure bugs that occur when the previous WP_Query object
* is not destroyed properly before another is set up.
*
* @since 2.3.0
*
* @global WP_Query $wp_query WordPress Query object.
* @global WP_Query $wp_the_query Copy of the global WP_Query instance created during wp_reset_query().
*/
function wp_reset_query() {
$GLOBALS['wp_query'] = $GLOBALS['wp_the_query'];
wp_reset_postdata();
}
/**
* After looping through a separate query, this function restores
* the $post global to the current post in the main query.
*
* @since 3.0.0
*
* @global WP_Query $wp_query WordPress Query object.
*/
function wp_reset_postdata() {
global $wp_query;
if ( isset( $wp_query ) ) {
$wp_query->reset_postdata();
}
}
/*
* Query type checks.
*/
/**
* Determines whether the query is for an existing archive page.
*
* Archive pages include category, tag, author, date, custom post type,
* and custom taxonomy based archives.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 1.5.0
*
* @see is_category()
* @see is_tag()
* @see is_author()
* @see is_date()
* @see is_post_type_archive()
* @see is_tax()
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is for an existing archive page.
*/
function is_archive() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_archive();
}
/**
* Determines whether the query is for an existing post type archive page.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 3.1.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @param string|string[] $post_types Optional. Post type or array of posts types
* to check against. Default empty.
* @return bool Whether the query is for an existing post type archive page.
*/
function is_post_type_archive( $post_types = '' ) {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_post_type_archive( $post_types );
}
/**
* Determines whether the query is for an existing attachment page.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 2.0.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @param int|string|int[]|string[] $attachment Optional. Attachment ID, title, slug, or array of such
* to check against. Default empty.
* @return bool Whether the query is for an existing attachment page.
*/
function is_attachment( $attachment = '' ) {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_attachment( $attachment );
}
/**
* Determines whether the query is for an existing author archive page.
*
* If the $author parameter is specified, this function will additionally
* check if the query is for one of the authors specified.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 1.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @param int|string|int[]|string[] $author Optional. User ID, nickname, nicename, or array of such
* to check against. Default empty.
* @return bool Whether the query is for an existing author archive page.
*/
function is_author( $author = '' ) {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_author( $author );
}
/**
* Determines whether the query is for an existing category archive page.
*
* If the $category parameter is specified, this function will additionally
* check if the query is for one of the categories specified.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 1.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @param int|string|int[]|string[] $category Optional. Category ID, name, slug, or array of such
* to check against. Default empty.
* @return bool Whether the query is for an existing category archive page.
*/
function is_category( $category = '' ) {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_category( $category );
}
/**
* Determines whether the query is for an existing tag archive page.
*
* If the $tag parameter is specified, this function will additionally
* check if the query is for one of the tags specified.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 2.3.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @param int|string|int[]|string[] $tag Optional. Tag ID, name, slug, or array of such
* to check against. Default empty.
* @return bool Whether the query is for an existing tag archive page.
*/
function is_tag( $tag = '' ) {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_tag( $tag );
}
/**
* Determines whether the query is for an existing custom taxonomy archive page.
*
* If the $taxonomy parameter is specified, this function will additionally
* check if the query is for that specific $taxonomy.
*
* If the $term parameter is specified in addition to the $taxonomy parameter,
* this function will additionally check if the query is for one of the terms
* specified.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 2.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @param string|string[] $taxonomy Optional. Taxonomy slug or slugs to check against.
* Default empty.
* @param int|string|int[]|string[] $term Optional. Term ID, name, slug, or array of such
* to check against. Default empty.
* @return bool Whether the query is for an existing custom taxonomy archive page.
* True for custom taxonomy archive pages, false for built-in taxonomies
* (category and tag archives).
*/
function is_tax( $taxonomy = '', $term = '' ) {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_tax( $taxonomy, $term );
}
/**
* Determines whether the query is for an existing date archive.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 1.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is for an existing date archive.
*/
function is_date() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_date();
}
/**
* Determines whether the query is for an existing day archive.
*
* A conditional check to test whether the page is a date-based archive page displaying posts for the current day.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 1.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is for an existing day archive.
*/
function is_day() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_day();
}
/**
* Determines whether the query is for a feed.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 1.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @param string|string[] $feeds Optional. Feed type or array of feed types
* to check against. Default empty.
* @return bool Whether the query is for a feed.
*/
function is_feed( $feeds = '' ) {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_feed( $feeds );
}
/**
* Is the query for a comments feed?
*
* @since 3.0.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is for a comments feed.
*/
function is_comment_feed() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_comment_feed();
}
/**
* Determines whether the query is for the front page of the site.
*
* This is for what is displayed at your site's main URL.
*
* Depends on the site's "Front page displays" Reading Settings 'show_on_front' and 'page_on_front'.
*
* If you set a static page for the front page of your site, this function will return
* true when viewing that page.
*
* Otherwise the same as {@see is_home()}.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 2.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is for the front page of the site.
*/
function is_front_page() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_front_page();
}
/**
* Determines whether the query is for the blog homepage.
*
* The blog homepage is the page that shows the time-based blog content of the site.
*
* is_home() is dependent on the site's "Front page displays" Reading Settings 'show_on_front'
* and 'page_for_posts'.
*
* If a static page is set for the front page of the site, this function will return true only
* on the page you set as the "Posts page".
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 1.5.0
*
* @see is_front_page()
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is for the blog homepage.
*/
function is_home() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_home();
}
/**
* Determines whether the query is for the Privacy Policy page.
*
* The Privacy Policy page is the page that shows the Privacy Policy content of the site.
*
* is_privacy_policy() is dependent on the site's "Change your Privacy Policy page" Privacy Settings 'wp_page_for_privacy_policy'.
*
* This function will return true only on the page you set as the "Privacy Policy page".
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 5.2.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is for the Privacy Policy page.
*/
function is_privacy_policy() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_privacy_policy();
}
/**
* Determines whether the query is for an existing month archive.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 1.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is for an existing month archive.
*/
function is_month() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_month();
}
/**
* Determines whether the query is for an existing single page.
*
* If the $page parameter is specified, this function will additionally
* check if the query is for one of the pages specified.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 1.5.0
*
* @see is_single()
* @see is_singular()
* @global WP_Query $wp_query WordPress Query object.
*
* @param int|string|int[]|string[] $page Optional. Page ID, title, slug, or array of such
* to check against. Default empty.
* @return bool Whether the query is for an existing single page.
*/
function is_page( $page = '' ) {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_page( $page );
}
/**
* Determines whether the query is for a paged result and not for the first page.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 1.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is for a paged result.
*/
function is_paged() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_paged();
}
/**
* Determines whether the query is for a post or page preview.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 2.0.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is for a post or page preview.
*/
function is_preview() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_preview();
}
/**
* Is the query for the robots.txt file?
*
* @since 2.1.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is for the robots.txt file.
*/
function is_robots() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_robots();
}
/**
* Is the query for the favicon.ico file?
*
* @since 5.4.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is for the favicon.ico file.
*/
function is_favicon() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_favicon();
}
/**
* Determines whether the query is for a search.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 1.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is for a search.
*/
function is_search() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_search();
}
/**
* Determines whether the query is for an existing single post.
*
* Works for any post type, except attachments and pages
*
* If the $post parameter is specified, this function will additionally
* check if the query is for one of the Posts specified.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 1.5.0
*
* @see is_page()
* @see is_singular()
* @global WP_Query $wp_query WordPress Query object.
*
* @param int|string|int[]|string[] $post Optional. Post ID, title, slug, or array of such
* to check against. Default empty.
* @return bool Whether the query is for an existing single post.
*/
function is_single( $post = '' ) {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_single( $post );
}
/**
* Determines whether the query is for an existing single post of any post type
* (post, attachment, page, custom post types).
*
* If the $post_types parameter is specified, this function will additionally
* check if the query is for one of the Posts Types specified.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 1.5.0
*
* @see is_page()
* @see is_single()
* @global WP_Query $wp_query WordPress Query object.
*
* @param string|string[] $post_types Optional. Post type or array of post types
* to check against. Default empty.
* @return bool Whether the query is for an existing single post
* or any of the given post types.
*/
function is_singular( $post_types = '' ) {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_singular( $post_types );
}
/**
* Determines whether the query is for a specific time.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 1.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is for a specific time.
*/
function is_time() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_time();
}
/**
* Determines whether the query is for a trackback endpoint call.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 1.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is for a trackback endpoint call.
*/
function is_trackback() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_trackback();
}
/**
* Determines whether the query is for an existing year archive.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 1.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is for an existing year archive.
*/
function is_year() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_year();
}
/**
* Determines whether the query has resulted in a 404 (returns no results).
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 1.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is a 404 error.
*/
function is_404() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_404();
}
/**
* Is the query for an embedded post?
*
* @since 4.4.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is for an embedded post.
*/
function is_embed() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
return false;
}
return $wp_query->is_embed();
}
/**
* Determines whether the query is the main query.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 3.3.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool Whether the query is the main query.
*/
function is_main_query() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '6.1.0' );
return false;
}
if ( 'pre_get_posts' === current_filter() ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: pre_get_posts, 2: WP_Query->is_main_query(), 3: is_main_query(), 4: Documentation URL. */
__( 'In %1$s, use the %2$s method, not the %3$s function. See %4$s.' ),
'pre_get_posts
',
'WP_Query->is_main_query()
',
'is_main_query()
',
__( 'https://developer.wordpress.org/reference/functions/is_main_query/' )
),
'3.7.0'
);
}
return $wp_query->is_main_query();
}
/*
* The Loop. Post loop control.
*/
/**
* Determines whether current WordPress query has posts to loop over.
*
* @since 1.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool True if posts are available, false if end of the loop.
*/
function have_posts() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
return false;
}
return $wp_query->have_posts();
}
/**
* Determines whether the caller is in the Loop.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 2.0.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool True if caller is within loop, false if loop hasn't started or ended.
*/
function in_the_loop() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
return false;
}
return $wp_query->in_the_loop;
}
/**
* Rewind the loop posts.
*
* @since 1.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*/
function rewind_posts() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
return;
}
$wp_query->rewind_posts();
}
/**
* Iterate the post index in the loop.
*
* @since 1.5.0
*
* @global WP_Query $wp_query WordPress Query object.
*/
function the_post() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
return;
}
$wp_query->the_post();
}
/*
* Comments loop.
*/
/**
* Determines whether current WordPress query has comments to loop over.
*
* @since 2.2.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @return bool True if comments are available, false if no more comments.
*/
function have_comments() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
return false;
}
return $wp_query->have_comments();
}
/**
* Iterate comment index in the comment loop.
*
* @since 2.2.0
*
* @global WP_Query $wp_query WordPress Query object.
*/
function the_comment() {
global $wp_query;
if ( ! isset( $wp_query ) ) {
return;
}
$wp_query->the_comment();
}
/**
* Redirect old slugs to the correct permalink.
*
* Attempts to find the current slug from the past slugs.
*
* @since 2.1.0
*/
function wp_old_slug_redirect() {
if ( is_404() && '' !== get_query_var( 'name' ) ) {
// Guess the current post type based on the query vars.
if ( get_query_var( 'post_type' ) ) {
$post_type = get_query_var( 'post_type' );
} elseif ( get_query_var( 'attachment' ) ) {
$post_type = 'attachment';
} elseif ( get_query_var( 'pagename' ) ) {
$post_type = 'page';
} else {
$post_type = 'post';
}
if ( is_array( $post_type ) ) {
if ( count( $post_type ) > 1 ) {
return;
}
$post_type = reset( $post_type );
}
// Do not attempt redirect for hierarchical post types.
if ( is_post_type_hierarchical( $post_type ) ) {
return;
}
$id = _find_post_by_old_slug( $post_type );
if ( ! $id ) {
$id = _find_post_by_old_date( $post_type );
}
/**
* Filters the old slug redirect post ID.
*
* @since 4.9.3
*
* @param int $id The redirect post ID.
*/
$id = apply_filters( 'old_slug_redirect_post_id', $id );
if ( ! $id ) {
return;
}
$link = get_permalink( $id );
if ( get_query_var( 'paged' ) > 1 ) {
$link = user_trailingslashit( trailingslashit( $link ) . 'page/' . get_query_var( 'paged' ) );
} elseif ( is_embed() ) {
$link = user_trailingslashit( trailingslashit( $link ) . 'embed' );
}
/**
* Filters the old slug redirect URL.
*
* @since 4.4.0
*
* @param string $link The redirect URL.
*/
$link = apply_filters( 'old_slug_redirect_url', $link );
if ( ! $link ) {
return;
}
wp_redirect( $link, 301 ); // Permanent redirect.
exit;
}
}
/**
* Find the post ID for redirecting an old slug.
*
* @since 4.9.3
* @access private
*
* @see wp_old_slug_redirect()
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param string $post_type The current post type based on the query vars.
* @return int The Post ID.
*/
function _find_post_by_old_slug( $post_type ) {
global $wpdb;
$query = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta, $wpdb->posts WHERE ID = post_id AND post_type = %s AND meta_key = '_wp_old_slug' AND meta_value = %s", $post_type, get_query_var( 'name' ) );
/*
* If year, monthnum, or day have been specified, make our query more precise
* just in case there are multiple identical _wp_old_slug values.
*/
if ( get_query_var( 'year' ) ) {
$query .= $wpdb->prepare( ' AND YEAR(post_date) = %d', get_query_var( 'year' ) );
}
if ( get_query_var( 'monthnum' ) ) {
$query .= $wpdb->prepare( ' AND MONTH(post_date) = %d', get_query_var( 'monthnum' ) );
}
if ( get_query_var( 'day' ) ) {
$query .= $wpdb->prepare( ' AND DAYOFMONTH(post_date) = %d', get_query_var( 'day' ) );
}
$key = md5( $query );
$last_changed = wp_cache_get_last_changed( 'posts' );
$cache_key = "find_post_by_old_slug:$key:$last_changed";
$cache = wp_cache_get( $cache_key, 'post-queries' );
if ( false !== $cache ) {
$id = $cache;
} else {
$id = (int) $wpdb->get_var( $query );
wp_cache_set( $cache_key, $id, 'post-queries' );
}
return $id;
}
/**
* Find the post ID for redirecting an old date.
*
* @since 4.9.3
* @access private
*
* @see wp_old_slug_redirect()
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param string $post_type The current post type based on the query vars.
* @return int The Post ID.
*/
function _find_post_by_old_date( $post_type ) {
global $wpdb;
$date_query = '';
if ( get_query_var( 'year' ) ) {
$date_query .= $wpdb->prepare( ' AND YEAR(pm_date.meta_value) = %d', get_query_var( 'year' ) );
}
if ( get_query_var( 'monthnum' ) ) {
$date_query .= $wpdb->prepare( ' AND MONTH(pm_date.meta_value) = %d', get_query_var( 'monthnum' ) );
}
if ( get_query_var( 'day' ) ) {
$date_query .= $wpdb->prepare( ' AND DAYOFMONTH(pm_date.meta_value) = %d', get_query_var( 'day' ) );
}
$id = 0;
if ( $date_query ) {
$query = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta AS pm_date, $wpdb->posts WHERE ID = post_id AND post_type = %s AND meta_key = '_wp_old_date' AND post_name = %s" . $date_query, $post_type, get_query_var( 'name' ) );
$key = md5( $query );
$last_changed = wp_cache_get_last_changed( 'posts' );
$cache_key = "find_post_by_old_date:$key:$last_changed";
$cache = wp_cache_get( $cache_key, 'post-queries' );
if ( false !== $cache ) {
$id = $cache;
} else {
$id = (int) $wpdb->get_var( $query );
if ( ! $id ) {
// Check to see if an old slug matches the old date.
$id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts, $wpdb->postmeta AS pm_slug, $wpdb->postmeta AS pm_date WHERE ID = pm_slug.post_id AND ID = pm_date.post_id AND post_type = %s AND pm_slug.meta_key = '_wp_old_slug' AND pm_slug.meta_value = %s AND pm_date.meta_key = '_wp_old_date'" . $date_query, $post_type, get_query_var( 'name' ) ) );
}
wp_cache_set( $cache_key, $id, 'post-queries' );
}
}
return $id;
}
/**
* Set up global post data.
*
* @since 1.5.0
* @since 4.4.0 Added the ability to pass a post ID to `$post`.
*
* @global WP_Query $wp_query WordPress Query object.
*
* @param WP_Post|object|int $post WP_Post instance or Post ID/object.
* @return bool True when finished.
*/
function setup_postdata( $post ) {
global $wp_query;
if ( ! empty( $wp_query ) && $wp_query instanceof WP_Query ) {
return $wp_query->setup_postdata( $post );
}
return false;
}
/**
* Generates post data.
*
* @since 5.2.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @param WP_Post|object|int $post WP_Post instance or Post ID/object.
* @return array|false Elements of post, or false on failure.
*/
function generate_postdata( $post ) {
global $wp_query;
if ( ! empty( $wp_query ) && $wp_query instanceof WP_Query ) {
return $wp_query->generate_postdata( $post );
}
return false;
}
/**
* Class for generating SQL clauses that filter a primary query according to date.
*
* WP_Date_Query is a helper that allows primary query classes, such as WP_Query, to filter
* their results by date columns, by generating `WHERE` subclauses to be attached to the
* primary SQL query string.
*
* Attempting to filter by an invalid date value (eg month=13) will generate SQL that will
* return no results. In these cases, a _doing_it_wrong() error notice is also thrown.
* See WP_Date_Query::validate_date_values().
*
* @link https://developer.wordpress.org/reference/classes/wp_query/
*
* @since 3.7.0
*/
#[AllowDynamicProperties]
class WP_Date_Query {
/**
* Array of date queries.
*
* See WP_Date_Query::__construct() for information on date query arguments.
*
* @since 3.7.0
* @var array
*/
public $queries = array();
/**
* The default relation between top-level queries. Can be either 'AND' or 'OR'.
*
* @since 3.7.0
* @var string
*/
public $relation = 'AND';
/**
* The column to query against. Can be changed via the query arguments.
*
* @since 3.7.0
* @var string
*/
public $column = 'post_date';
/**
* The value comparison operator. Can be changed via the query arguments.
*
* @since 3.7.0
* @var string
*/
public $compare = '=';
/**
* Supported time-related parameter keys.
*
* @since 4.1.0
* @var string[]
*/
public $time_keys = array( 'after', 'before', 'year', 'month', 'monthnum', 'week', 'w', 'dayofyear', 'day', 'dayofweek', 'dayofweek_iso', 'hour', 'minute', 'second' );
/**
* Constructor.
*
* Time-related parameters that normally require integer values ('year', 'month', 'week', 'dayofyear', 'day',
* 'dayofweek', 'dayofweek_iso', 'hour', 'minute', 'second') accept arrays of integers for some values of
* 'compare'. When 'compare' is 'IN' or 'NOT IN', arrays are accepted; when 'compare' is 'BETWEEN' or 'NOT
* BETWEEN', arrays of two valid values are required. See individual argument descriptions for accepted values.
*
* @since 3.7.0
* @since 4.0.0 The $inclusive logic was updated to include all times within the date range.
* @since 4.1.0 Introduced 'dayofweek_iso' time type parameter.
*
* @param array $date_query {
* Array of date query clauses.
*
* @type array ...$0 {
* @type string $column Optional. The column to query against. If undefined, inherits the value of
* the `$default_column` parameter. See WP_Date_Query::validate_column() and
* the {@see 'date_query_valid_columns'} filter for the list of accepted values.
* Default 'post_date'.
* @type string $compare Optional. The comparison operator. Accepts '=', '!=', '>', '>=', '<', '<=',
* 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'. Default '='.
* @type string $relation Optional. The boolean relationship between the date queries. Accepts 'OR' or 'AND'.
* Default 'OR'.
* @type array ...$0 {
* Optional. An array of first-order clause parameters, or another fully-formed date query.
*
* @type string|array $before {
* Optional. Date to retrieve posts before. Accepts `strtotime()`-compatible string,
* or array of 'year', 'month', 'day' values.
*
* @type string $year The four-digit year. Default empty. Accepts any four-digit year.
* @type string $month Optional when passing array.The month of the year.
* Default (string:empty)|(array:1). Accepts numbers 1-12.
* @type string $day Optional when passing array.The day of the month.
* Default (string:empty)|(array:1). Accepts numbers 1-31.
* }
* @type string|array $after {
* Optional. Date to retrieve posts after. Accepts `strtotime()`-compatible string,
* or array of 'year', 'month', 'day' values.
*
* @type string $year The four-digit year. Accepts any four-digit year. Default empty.
* @type string $month Optional when passing array. The month of the year. Accepts numbers 1-12.
* Default (string:empty)|(array:12).
* @type string $day Optional when passing array.The day of the month. Accepts numbers 1-31.
* Default (string:empty)|(array:last day of month).
* }
* @type string $column Optional. Used to add a clause comparing a column other than
* the column specified in the top-level `$column` parameter.
* See WP_Date_Query::validate_column() and
* the {@see 'date_query_valid_columns'} filter for the list
* of accepted values. Default is the value of top-level `$column`.
* @type string $compare Optional. The comparison operator. Accepts '=', '!=', '>', '>=',
* '<', '<=', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'. 'IN',
* 'NOT IN', 'BETWEEN', and 'NOT BETWEEN'. Comparisons support
* arrays in some time-related parameters. Default '='.
* @type bool $inclusive Optional. Include results from dates specified in 'before' or
* 'after'. Default false.
* @type int|int[] $year Optional. The four-digit year number. Accepts any four-digit year
* or an array of years if `$compare` supports it. Default empty.
* @type int|int[] $month Optional. The two-digit month number. Accepts numbers 1-12 or an
* array of valid numbers if `$compare` supports it. Default empty.
* @type int|int[] $week Optional. The week number of the year. Accepts numbers 0-53 or an
* array of valid numbers if `$compare` supports it. Default empty.
* @type int|int[] $dayofyear Optional. The day number of the year. Accepts numbers 1-366 or an
* array of valid numbers if `$compare` supports it.
* @type int|int[] $day Optional. The day of the month. Accepts numbers 1-31 or an array
* of valid numbers if `$compare` supports it. Default empty.
* @type int|int[] $dayofweek Optional. The day number of the week. Accepts numbers 1-7 (1 is
* Sunday) or an array of valid numbers if `$compare` supports it.
* Default empty.
* @type int|int[] $dayofweek_iso Optional. The day number of the week (ISO). Accepts numbers 1-7
* (1 is Monday) or an array of valid numbers if `$compare` supports it.
* Default empty.
* @type int|int[] $hour Optional. The hour of the day. Accepts numbers 0-23 or an array
* of valid numbers if `$compare` supports it. Default empty.
* @type int|int[] $minute Optional. The minute of the hour. Accepts numbers 0-59 or an array
* of valid numbers if `$compare` supports it. Default empty.
* @type int|int[] $second Optional. The second of the minute. Accepts numbers 0-59 or an
* array of valid numbers if `$compare` supports it. Default empty.
* }
* }
* }
* @param string $default_column Optional. Default column to query against. See WP_Date_Query::validate_column()
* and the {@see 'date_query_valid_columns'} filter for the list of accepted values.
* Default 'post_date'.
*/
public function __construct( $date_query, $default_column = 'post_date' ) {
if ( empty( $date_query ) || ! is_array( $date_query ) ) {
return;
}
if ( isset( $date_query['relation'] ) ) {
$this->relation = $this->sanitize_relation( $date_query['relation'] );
} else {
$this->relation = 'AND';
}
// Support for passing time-based keys in the top level of the $date_query array.
if ( ! isset( $date_query[0] ) ) {
$date_query = array( $date_query );
}
if ( ! empty( $date_query['column'] ) ) {
$date_query['column'] = esc_sql( $date_query['column'] );
} else {
$date_query['column'] = esc_sql( $default_column );
}
$this->column = $this->validate_column( $this->column );
$this->compare = $this->get_compare( $date_query );
$this->queries = $this->sanitize_query( $date_query );
}
/**
* Recursive-friendly query sanitizer.
*
* Ensures that each query-level clause has a 'relation' key, and that
* each first-order clause contains all the necessary keys from `$defaults`.
*
* @since 4.1.0
*
* @param array $queries
* @param array $parent_query
* @return array Sanitized queries.
*/
public function sanitize_query( $queries, $parent_query = null ) {
$cleaned_query = array();
$defaults = array(
'column' => 'post_date',
'compare' => '=',
'relation' => 'AND',
);
// Numeric keys should always have array values.
foreach ( $queries as $qkey => $qvalue ) {
if ( is_numeric( $qkey ) && ! is_array( $qvalue ) ) {
unset( $queries[ $qkey ] );
}
}
// Each query should have a value for each default key. Inherit from the parent when possible.
foreach ( $defaults as $dkey => $dvalue ) {
if ( isset( $queries[ $dkey ] ) ) {
continue;
}
if ( isset( $parent_query[ $dkey ] ) ) {
$queries[ $dkey ] = $parent_query[ $dkey ];
} else {
$queries[ $dkey ] = $dvalue;
}
}
// Validate the dates passed in the query.
if ( $this->is_first_order_clause( $queries ) ) {
$this->validate_date_values( $queries );
}
// Sanitize the relation parameter.
$queries['relation'] = $this->sanitize_relation( $queries['relation'] );
foreach ( $queries as $key => $q ) {
if ( ! is_array( $q ) || in_array( $key, $this->time_keys, true ) ) {
// This is a first-order query. Trust the values and sanitize when building SQL.
$cleaned_query[ $key ] = $q;
} else {
// Any array without a time key is another query, so we recurse.
$cleaned_query[] = $this->sanitize_query( $q, $queries );
}
}
return $cleaned_query;
}
/**
* Determines whether this is a first-order clause.
*
* Checks to see if the current clause has any time-related keys.
* If so, it's first-order.
*
* @since 4.1.0
*
* @param array $query Query clause.
* @return bool True if this is a first-order clause.
*/
protected function is_first_order_clause( $query ) {
$time_keys = array_intersect( $this->time_keys, array_keys( $query ) );
return ! empty( $time_keys );
}
/**
* Determines and validates what comparison operator to use.
*
* @since 3.7.0
*
* @param array $query A date query or a date subquery.
* @return string The comparison operator.
*/
public function get_compare( $query ) {
if ( ! empty( $query['compare'] )
&& in_array( $query['compare'], array( '=', '!=', '>', '>=', '<', '<=', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ), true )
) {
return strtoupper( $query['compare'] );
}
return $this->compare;
}
/**
* Validates the given date_query values and triggers errors if something is not valid.
*
* Note that date queries with invalid date ranges are allowed to
* continue (though of course no items will be found for impossible dates).
* This method only generates debug notices for these cases.
*
* @since 4.1.0
*
* @param array $date_query The date_query array.
* @return bool True if all values in the query are valid, false if one or more fail.
*/
public function validate_date_values( $date_query = array() ) {
if ( empty( $date_query ) ) {
return false;
}
$valid = true;
/*
* Validate 'before' and 'after' up front, then let the
* validation routine continue to be sure that all invalid
* values generate errors too.
*/
if ( array_key_exists( 'before', $date_query ) && is_array( $date_query['before'] ) ) {
$valid = $this->validate_date_values( $date_query['before'] );
}
if ( array_key_exists( 'after', $date_query ) && is_array( $date_query['after'] ) ) {
$valid = $this->validate_date_values( $date_query['after'] );
}
// Array containing all min-max checks.
$min_max_checks = array();
// Days per year.
if ( array_key_exists( 'year', $date_query ) ) {
/*
* If a year exists in the date query, we can use it to get the days.
* If multiple years are provided (as in a BETWEEN), use the first one.
*/
if ( is_array( $date_query['year'] ) ) {
$_year = reset( $date_query['year'] );
} else {
$_year = $date_query['year'];
}
$max_days_of_year = gmdate( 'z', mktime( 0, 0, 0, 12, 31, $_year ) ) + 1;
} else {
// Otherwise we use the max of 366 (leap-year).
$max_days_of_year = 366;
}
$min_max_checks['dayofyear'] = array(
'min' => 1,
'max' => $max_days_of_year,
);
// Days per week.
$min_max_checks['dayofweek'] = array(
'min' => 1,
'max' => 7,
);
// Days per week.
$min_max_checks['dayofweek_iso'] = array(
'min' => 1,
'max' => 7,
);
// Months per year.
$min_max_checks['month'] = array(
'min' => 1,
'max' => 12,
);
// Weeks per year.
if ( isset( $_year ) ) {
/*
* If we have a specific year, use it to calculate number of weeks.
* Note: the number of weeks in a year is the date in which Dec 28 appears.
*/
$week_count = gmdate( 'W', mktime( 0, 0, 0, 12, 28, $_year ) );
} else {
// Otherwise set the week-count to a maximum of 53.
$week_count = 53;
}
$min_max_checks['week'] = array(
'min' => 1,
'max' => $week_count,
);
// Days per month.
$min_max_checks['day'] = array(
'min' => 1,
'max' => 31,
);
// Hours per day.
$min_max_checks['hour'] = array(
'min' => 0,
'max' => 23,
);
// Minutes per hour.
$min_max_checks['minute'] = array(
'min' => 0,
'max' => 59,
);
// Seconds per minute.
$min_max_checks['second'] = array(
'min' => 0,
'max' => 59,
);
// Concatenate and throw a notice for each invalid value.
foreach ( $min_max_checks as $key => $check ) {
if ( ! array_key_exists( $key, $date_query ) ) {
continue;
}
// Throw a notice for each failing value.
foreach ( (array) $date_query[ $key ] as $_value ) {
$is_between = $_value >= $check['min'] && $_value <= $check['max'];
if ( ! is_numeric( $_value ) || ! $is_between ) {
$error = sprintf(
/* translators: Date query invalid date message. 1: Invalid value, 2: Type of value, 3: Minimum valid value, 4: Maximum valid value. */
__( 'Invalid value %1$s for %2$s. Expected value should be between %3$s and %4$s.' ),
'' . esc_html( $_value ) . '
',
'' . esc_html( $key ) . '
',
'' . esc_html( $check['min'] ) . '
',
'' . esc_html( $check['max'] ) . '
'
);
_doing_it_wrong( __CLASS__, $error, '4.1.0' );
$valid = false;
}
}
}
// If we already have invalid date messages, don't bother running through checkdate().
if ( ! $valid ) {
return $valid;
}
$day_month_year_error_msg = '';
$day_exists = array_key_exists( 'day', $date_query ) && is_numeric( $date_query['day'] );
$month_exists = array_key_exists( 'month', $date_query ) && is_numeric( $date_query['month'] );
$year_exists = array_key_exists( 'year', $date_query ) && is_numeric( $date_query['year'] );
if ( $day_exists && $month_exists && $year_exists ) {
// 1. Checking day, month, year combination.
if ( ! wp_checkdate( $date_query['month'], $date_query['day'], $date_query['year'], sprintf( '%s-%s-%s', $date_query['year'], $date_query['month'], $date_query['day'] ) ) ) {
$day_month_year_error_msg = sprintf(
/* translators: 1: Year, 2: Month, 3: Day of month. */
__( 'The following values do not describe a valid date: year %1$s, month %2$s, day %3$s.' ),
'' . esc_html( $date_query['year'] ) . '
',
'' . esc_html( $date_query['month'] ) . '
',
'' . esc_html( $date_query['day'] ) . '
'
);
$valid = false;
}
} elseif ( $day_exists && $month_exists ) {
/*
* 2. checking day, month combination
* We use 2012 because, as a leap year, it's the most permissive.
*/
if ( ! wp_checkdate( $date_query['month'], $date_query['day'], 2012, sprintf( '2012-%s-%s', $date_query['month'], $date_query['day'] ) ) ) {
$day_month_year_error_msg = sprintf(
/* translators: 1: Month, 2: Day of month. */
__( 'The following values do not describe a valid date: month %1$s, day %2$s.' ),
'' . esc_html( $date_query['month'] ) . '
',
'' . esc_html( $date_query['day'] ) . '
'
);
$valid = false;
}
}
if ( ! empty( $day_month_year_error_msg ) ) {
_doing_it_wrong( __CLASS__, $day_month_year_error_msg, '4.1.0' );
}
return $valid;
}
/**
* Validates a column name parameter.
*
* Column names without a table prefix (like 'post_date') are checked against a list of
* allowed and known tables, and then, if found, have a table prefix (such as 'wp_posts.')
* prepended. Prefixed column names (such as 'wp_posts.post_date') bypass this allowed
* check, and are only sanitized to remove illegal characters.
*
* @since 3.7.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param string $column The user-supplied column name.
* @return string A validated column name value.
*/
public function validate_column( $column ) {
global $wpdb;
$valid_columns = array(
'post_date',
'post_date_gmt',
'post_modified',
'post_modified_gmt',
'comment_date',
'comment_date_gmt',
'user_registered',
'registered',
'last_updated',
);
// Attempt to detect a table prefix.
if ( ! str_contains( $column, '.' ) ) {
/**
* Filters the list of valid date query columns.
*
* @since 3.7.0
* @since 4.1.0 Added 'user_registered' to the default recognized columns.
* @since 4.6.0 Added 'registered' and 'last_updated' to the default recognized columns.
*
* @param string[] $valid_columns An array of valid date query columns. Defaults
* are 'post_date', 'post_date_gmt', 'post_modified',
* 'post_modified_gmt', 'comment_date', 'comment_date_gmt',
* 'user_registered', 'registered', 'last_updated'.
*/
if ( ! in_array( $column, apply_filters( 'date_query_valid_columns', $valid_columns ), true ) ) {
$column = 'post_date';
}
$known_columns = array(
$wpdb->posts => array(
'post_date',
'post_date_gmt',
'post_modified',
'post_modified_gmt',
),
$wpdb->comments => array(
'comment_date',
'comment_date_gmt',
),
$wpdb->users => array(
'user_registered',
),
$wpdb->blogs => array(
'registered',
'last_updated',
),
);
// If it's a known column name, add the appropriate table prefix.
foreach ( $known_columns as $table_name => $table_columns ) {
if ( in_array( $column, $table_columns, true ) ) {
$column = $table_name . '.' . $column;
break;
}
}
}
// Remove unsafe characters.
return preg_replace( '/[^a-zA-Z0-9_$\.]/', '', $column );
}
/**
* Generates WHERE clause to be appended to a main query.
*
* @since 3.7.0
*
* @return string MySQL WHERE clause.
*/
public function get_sql() {
$sql = $this->get_sql_clauses();
$where = $sql['where'];
/**
* Filters the date query WHERE clause.
*
* @since 3.7.0
*
* @param string $where WHERE clause of the date query.
* @param WP_Date_Query $query The WP_Date_Query instance.
*/
return apply_filters( 'get_date_sql', $where, $this );
}
/**
* Generates SQL clauses to be appended to a main query.
*
* Called by the public WP_Date_Query::get_sql(), this method is abstracted
* out to maintain parity with the other Query classes.
*
* @since 4.1.0
*
* @return string[] {
* 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() {
$sql = $this->get_sql_for_query( $this->queries );
if ( ! empty( $sql['where'] ) ) {
$sql['where'] = ' AND ' . $sql['where'];
}
return $sql;
}
/**
* Generates 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
*
* @param array $query Query to parse.
* @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 );
$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;
}
/**
* Turns a single date clause into pieces for a WHERE clause.
*
* A wrapper for get_sql_for_clause(), included here for backward
* compatibility while retaining the naming convention across Query classes.
*
* @since 3.7.0
*
* @param array $query Date query arguments.
* @return array {
* Array containing JOIN and WHERE SQL clauses to append to the main query.
*
* @type string[] $join Array of SQL fragments to append to the main JOIN clause.
* @type string[] $where Array of SQL fragments to append to the main WHERE clause.
* }
*/
protected function get_sql_for_subquery( $query ) {
return $this->get_sql_for_clause( $query, '' );
}
/**
* Turns a first-order date query into SQL for a WHERE clause.
*
* @since 4.1.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param array $query Date query clause.
* @param array $parent_query Parent query of the current date query.
* @return array {
* Array containing JOIN and WHERE SQL clauses to append to the main query.
*
* @type string[] $join Array of SQL fragments to append to the main JOIN clause.
* @type string[] $where Array of SQL fragments to append to the main WHERE clause.
* }
*/
protected function get_sql_for_clause( $query, $parent_query ) {
global $wpdb;
// The sub-parts of a $where part.
$where_parts = array();
$column = ( ! empty( $query['column'] ) ) ? esc_sql( $query['column'] ) : $this->column;
$column = $this->validate_column( $column );
$compare = $this->get_compare( $query );
$inclusive = ! empty( $query['inclusive'] );
// Assign greater- and less-than values.
$lt = '<';
$gt = '>';
if ( $inclusive ) {
$lt .= '=';
$gt .= '=';
}
// Range queries.
if ( ! empty( $query['after'] ) ) {
$where_parts[] = $wpdb->prepare( "$column $gt %s", $this->build_mysql_datetime( $query['after'], ! $inclusive ) );
}
if ( ! empty( $query['before'] ) ) {
$where_parts[] = $wpdb->prepare( "$column $lt %s", $this->build_mysql_datetime( $query['before'], $inclusive ) );
}
// Specific value queries.
$date_units = array(
'YEAR' => array( 'year' ),
'MONTH' => array( 'month', 'monthnum' ),
'_wp_mysql_week' => array( 'week', 'w' ),
'DAYOFYEAR' => array( 'dayofyear' ),
'DAYOFMONTH' => array( 'day' ),
'DAYOFWEEK' => array( 'dayofweek' ),
'WEEKDAY' => array( 'dayofweek_iso' ),
);
// Check of the possible date units and add them to the query.
foreach ( $date_units as $sql_part => $query_parts ) {
foreach ( $query_parts as $query_part ) {
if ( isset( $query[ $query_part ] ) ) {
$value = $this->build_value( $compare, $query[ $query_part ] );
if ( $value ) {
switch ( $sql_part ) {
case '_wp_mysql_week':
$where_parts[] = _wp_mysql_week( $column ) . " $compare $value";
break;
case 'WEEKDAY':
$where_parts[] = "$sql_part( $column ) + 1 $compare $value";
break;
default:
$where_parts[] = "$sql_part( $column ) $compare $value";
}
break;
}
}
}
}
if ( isset( $query['hour'] ) || isset( $query['minute'] ) || isset( $query['second'] ) ) {
// Avoid notices.
foreach ( array( 'hour', 'minute', 'second' ) as $unit ) {
if ( ! isset( $query[ $unit ] ) ) {
$query[ $unit ] = null;
}
}
$time_query = $this->build_time_query( $column, $compare, $query['hour'], $query['minute'], $query['second'] );
if ( $time_query ) {
$where_parts[] = $time_query;
}
}
/*
* Return an array of 'join' and 'where' for compatibility
* with other query classes.
*/
return array(
'where' => $where_parts,
'join' => array(),
);
}
/**
* Builds and validates a value string based on the comparison operator.
*
* @since 3.7.0
*
* @param string $compare The compare operator to use.
* @param string|array $value The value.
* @return string|false|int The value to be used in SQL or false on error.
*/
public function build_value( $compare, $value ) {
if ( ! isset( $value ) ) {
return false;
}
switch ( $compare ) {
case 'IN':
case 'NOT IN':
$value = (array) $value;
// Remove non-numeric values.
$value = array_filter( $value, 'is_numeric' );
if ( empty( $value ) ) {
return false;
}
return '(' . implode( ',', array_map( 'intval', $value ) ) . ')';
case 'BETWEEN':
case 'NOT BETWEEN':
if ( ! is_array( $value ) || 2 !== count( $value ) ) {
$value = array( $value, $value );
} else {
$value = array_values( $value );
}
// If either value is non-numeric, bail.
foreach ( $value as $v ) {
if ( ! is_numeric( $v ) ) {
return false;
}
}
$value = array_map( 'intval', $value );
return $value[0] . ' AND ' . $value[1];
default:
if ( ! is_numeric( $value ) ) {
return false;
}
return (int) $value;
}
}
/**
* Builds a MySQL format date/time based on some query parameters.
*
* You can pass an array of values (year, month, etc.) with missing parameter values being defaulted to
* either the maximum or minimum values (controlled by the $default_to parameter). Alternatively you can
* pass a string that will be passed to date_create().
*
* @since 3.7.0
*
* @param string|array $datetime An array of parameters or a strtotime() string.
* @param bool $default_to_max Whether to round up incomplete dates. Supported by values
* of $datetime that are arrays, or string values that are a
* subset of MySQL date format ('Y', 'Y-m', 'Y-m-d', 'Y-m-d H:i').
* Default: false.
* @return string|false A MySQL format date/time or false on failure.
*/
public function build_mysql_datetime( $datetime, $default_to_max = false ) {
if ( ! is_array( $datetime ) ) {
/*
* Try to parse some common date formats, so we can detect
* the level of precision and support the 'inclusive' parameter.
*/
if ( preg_match( '/^(\d{4})$/', $datetime, $matches ) ) {
// Y
$datetime = array(
'year' => (int) $matches[1],
);
} elseif ( preg_match( '/^(\d{4})\-(\d{2})$/', $datetime, $matches ) ) {
// Y-m
$datetime = array(
'year' => (int) $matches[1],
'month' => (int) $matches[2],
);
} elseif ( preg_match( '/^(\d{4})\-(\d{2})\-(\d{2})$/', $datetime, $matches ) ) {
// Y-m-d
$datetime = array(
'year' => (int) $matches[1],
'month' => (int) $matches[2],
'day' => (int) $matches[3],
);
} elseif ( preg_match( '/^(\d{4})\-(\d{2})\-(\d{2}) (\d{2}):(\d{2})$/', $datetime, $matches ) ) {
// Y-m-d H:i
$datetime = array(
'year' => (int) $matches[1],
'month' => (int) $matches[2],
'day' => (int) $matches[3],
'hour' => (int) $matches[4],
'minute' => (int) $matches[5],
);
}
// If no match is found, we don't support default_to_max.
if ( ! is_array( $datetime ) ) {
$wp_timezone = wp_timezone();
// Assume local timezone if not provided.
$dt = date_create( $datetime, $wp_timezone );
if ( false === $dt ) {
return gmdate( 'Y-m-d H:i:s', false );
}
return $dt->setTimezone( $wp_timezone )->format( 'Y-m-d H:i:s' );
}
}
$datetime = array_map( 'absint', $datetime );
if ( ! isset( $datetime['year'] ) ) {
$datetime['year'] = current_time( 'Y' );
}
if ( ! isset( $datetime['month'] ) ) {
$datetime['month'] = ( $default_to_max ) ? 12 : 1;
}
if ( ! isset( $datetime['day'] ) ) {
$datetime['day'] = ( $default_to_max ) ? (int) gmdate( 't', mktime( 0, 0, 0, $datetime['month'], 1, $datetime['year'] ) ) : 1;
}
if ( ! isset( $datetime['hour'] ) ) {
$datetime['hour'] = ( $default_to_max ) ? 23 : 0;
}
if ( ! isset( $datetime['minute'] ) ) {
$datetime['minute'] = ( $default_to_max ) ? 59 : 0;
}
if ( ! isset( $datetime['second'] ) ) {
$datetime['second'] = ( $default_to_max ) ? 59 : 0;
}
return sprintf( '%04d-%02d-%02d %02d:%02d:%02d', $datetime['year'], $datetime['month'], $datetime['day'], $datetime['hour'], $datetime['minute'], $datetime['second'] );
}
/**
* Builds a query string for comparing time values (hour, minute, second).
*
* If just hour, minute, or second is set than a normal comparison will be done.
* However if multiple values are passed, a pseudo-decimal time will be created
* in order to be able to accurately compare against.
*
* @since 3.7.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param string $column The column to query against. Needs to be pre-validated!
* @param string $compare The comparison operator. Needs to be pre-validated!
* @param int|null $hour Optional. An hour value (0-23).
* @param int|null $minute Optional. A minute value (0-59).
* @param int|null $second Optional. A second value (0-59).
* @return string|false A query part or false on failure.
*/
public function build_time_query( $column, $compare, $hour = null, $minute = null, $second = null ) {
global $wpdb;
// Have to have at least one.
if ( ! isset( $hour ) && ! isset( $minute ) && ! isset( $second ) ) {
return false;
}
// Complex combined queries aren't supported for multi-value queries.
if ( in_array( $compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ), true ) ) {
$return = array();
$value = $this->build_value( $compare, $hour );
if ( false !== $value ) {
$return[] = "HOUR( $column ) $compare $value";
}
$value = $this->build_value( $compare, $minute );
if ( false !== $value ) {
$return[] = "MINUTE( $column ) $compare $value";
}
$value = $this->build_value( $compare, $second );
if ( false !== $value ) {
$return[] = "SECOND( $column ) $compare $value";
}
return implode( ' AND ', $return );
}
// Cases where just one unit is set.
if ( isset( $hour ) && ! isset( $minute ) && ! isset( $second ) ) {
$value = $this->build_value( $compare, $hour );
if ( false !== $value ) {
return "HOUR( $column ) $compare $value";
}
} elseif ( ! isset( $hour ) && isset( $minute ) && ! isset( $second ) ) {
$value = $this->build_value( $compare, $minute );
if ( false !== $value ) {
return "MINUTE( $column ) $compare $value";
}
} elseif ( ! isset( $hour ) && ! isset( $minute ) && isset( $second ) ) {
$value = $this->build_value( $compare, $second );
if ( false !== $value ) {
return "SECOND( $column ) $compare $value";
}
}
// Single units were already handled. Since hour & second isn't allowed, minute must to be set.
if ( ! isset( $minute ) ) {
return false;
}
$format = '';
$time = '';
// Hour.
if ( null !== $hour ) {
$format .= '%H.';
$time .= sprintf( '%02d', $hour ) . '.';
} else {
$format .= '0.';
$time .= '0.';
}
// Minute.
$format .= '%i';
$time .= sprintf( '%02d', $minute );
if ( isset( $second ) ) {
$format .= '%s';
$time .= sprintf( '%02d', $second );
}
return $wpdb->prepare( "DATE_FORMAT( $column, %s ) $compare %f", $format, $time );
}
/**
* Sanitizes a 'relation' operator.
*
* @since 6.0.3
*
* @param string $relation Raw relation key from the query argument.
* @return string Sanitized relation. Either 'AND' or 'OR'.
*/
public function sanitize_relation( $relation ) {
if ( 'OR' === strtoupper( $relation ) ) {
return 'OR';
} else {
return 'AND';
}
}
}