: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
// 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`
* @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 );
// Fetch the meta and go on if it's found.
$meta = get_metadata_by_mid( $meta_type, $meta_id );
$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 );
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:
* @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 ) );
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 );
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_commentmeta`
* @param int $meta_id Deleted metadata entry ID.
do_action( "deleted_{$meta_type}meta", $meta_id );
// Meta ID was not found.
* Updates the metadata cache for the specified objects.
* @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 ) {
if ( ! $meta_type || ! $object_ids ) {
$table = _get_meta_table( $meta_type );
$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`
* @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 );
$cache_key = $meta_type . '_meta';
$non_cached_ids = array();
$cache_values = wp_cache_get_multiple( $object_ids, $cache_key );
foreach ( $cache_values as $id => $cached_object ) {
if ( false === $cached_object ) {
$cache[ $id ] = $cached_object;
if ( empty( $non_cached_ids ) ) {
$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;
foreach ( $non_cached_ids as $id ) {
if ( ! isset( $cache[ $id ] ) ) {
$data[ $id ] = $cache[ $id ];
wp_cache_add_multiple( $data, $cache_key );
* Retrieves the queue for lazy-loading metadata.
* @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.
* @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.
* @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 ) {
$table_name = $type . 'meta';
if ( empty( $wpdb->$table_name ) ) {
return $wpdb->$table_name;
* Determines whether a meta key is considered protected.
* @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.
* @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 );
* @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.
* @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.
* @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 );
* 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 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.
* 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
* @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 ) {
if ( ! is_array( $wp_meta_keys ) ) {
'sanitize_callback' => null,
'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 ) ) {
'sanitize_callback' => $args,
$has_old_sanitize_cb = true;
if ( is_callable( $deprecated ) ) {
$args['auth_callback'] = $deprecated;
* Filters the registration arguments when registering meta.
* @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' );
$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' );
} 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' );
// 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';
$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 );
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 );