e( $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 ); } allows hosts to send users directly to * the page where they can update their site to use HTTPS. * * @since 5.7.0 * * @return string URL for directly updating to HTTPS or empty string. */ function wp_get_direct_update_https_url() { $direct_update_url = ''; if ( false !== getenv( 'WP_DIRECT_UPDATE_HTTPS_URL' ) ) { $direct_update_url = getenv( 'WP_DIRECT_UPDATE_HTTPS_URL' ); } /** * Filters the URL for directly updating the PHP version the site is running on from the host. * * @since 5.7.0 * * @param string $direct_update_url URL for directly updating PHP. */ $direct_update_url = apply_filters( 'wp_direct_update_https_url', $direct_update_url ); return $direct_update_url; } /** * Gets the size of a directory. * * A helper function that is used primarily to check whether * a blog has exceeded its allowed upload space. * * @since MU (3.0.0) * @since 5.2.0 $max_execution_time parameter added. * * @param string $directory Full path of a directory. * @param int $max_execution_time Maximum time to run before giving up. In seconds. * The timeout is global and is measured from the moment WordPress started to load. * @return int|false|null Size in bytes if a valid directory. False if not. Null if timeout. */ function get_dirsize( $directory, $max_execution_time = null ) { /* * Exclude individual site directories from the total when checking the main site of a network, * as they are subdirectories and should not be counted. */ if ( is_multisite() && is_main_site() ) { $size = recurse_dirsize( $directory, $directory . '/sites', $max_execution_time ); } else { $size = recurse_dirsize( $directory, null, $max_execution_time ); } return $size; } /** * Gets the size of a directory recursively. * * Used by get_dirsize() to get a directory size when it contains other directories. * * @since MU (3.0.0) * @since 4.3.0 The `$exclude` parameter was added. * @since 5.2.0 The `$max_execution_time` parameter was added. * @since 5.6.0 The `$directory_cache` parameter was added. * * @param string $directory Full path of a directory. * @param string|string[] $exclude Optional. Full path of a subdirectory to exclude from the total, * or array of paths. Expected without trailing slash(es). * Default null. * @param int $max_execution_time Optional. Maximum time to run before giving up. In seconds. * The timeout is global and is measured from the moment * WordPress started to load. Defaults to the value of * `max_execution_time` PHP setting. * @param array $directory_cache Optional. Array of cached directory paths. * Defaults to the value of `dirsize_cache` transient. * @return int|false|null Size in bytes if a valid directory. False if not. Null if timeout. */ function recurse_dirsize( $directory, $exclude = null, $max_execution_time = null, &$directory_cache = null ) { $directory = untrailingslashit( $directory ); $save_cache = false; if ( ! isset( $directory_cache ) ) { $directory_cache = get_transient( 'dirsize_cache' ); $save_cache = true; } if ( isset( $directory_cache[ $directory ] ) && is_int( $directory_cache[ $directory ] ) ) { return $directory_cache[ $directory ]; } if ( ! file_exists( $directory ) || ! is_dir( $directory ) || ! is_readable( $directory ) ) { return false; } if ( ( is_string( $exclude ) && $directory === $exclude ) || ( is_array( $exclude ) && in_array( $directory, $exclude, true ) ) ) { return false; } if ( null === $max_execution_time ) { // Keep the previous behavior but attempt to prevent fatal errors from timeout if possible. if ( function_exists( 'ini_get' ) ) { $max_execution_time = ini_get( 'max_execution_time' ); } else { // Disable... $max_execution_time = 0; } // Leave 1 second "buffer" for other operations if $max_execution_time has reasonable value. if ( $max_execution_time > 10 ) { $max_execution_time -= 1; } } /** * Filters the amount of storage space used by one directory and all its children, in megabytes. * * Return the actual used space to short-circuit the recursive PHP file size calculation * and use something else, like a CDN API or native operating system tools for better performance. * * @since 5.6.0 * * @param int|false $space_used The amount of used space, in bytes. Default false. * @param string $directory Full path of a directory. * @param string|string[]|null $exclude Full path of a subdirectory to exclude from the total, * or array of paths. * @param int $max_execution_time Maximum time to run before giving up. In seconds. * @param array $directory_cache Array of cached directory paths. */ $size = apply_filters( 'pre_recurse_dirsize', false, $directory, $exclude, $max_execution_time, $directory_cache ); if ( false === $size ) { $size = 0; $handle = opendir( $directory ); if ( $handle ) { while ( ( $file = readdir( $handle ) ) !== false ) { $path = $directory . '/' . $file; if ( '.' !== $file && '..' !== $file ) { if ( is_file( $path ) ) { $size += filesize( $path ); } elseif ( is_dir( $path ) ) { $handlesize = recurse_dirsize( $path, $exclude, $max_execution_time, $directory_cache ); if ( $handlesize > 0 ) { $size += $handlesize; } } if ( $max_execution_time > 0 && ( microtime( true ) - WP_START_TIMESTAMP ) > $max_execution_time ) { // Time exceeded. Give up instead of risking a fatal timeout. $size = null; break; } } } closedir( $handle ); } } if ( ! is_array( $directory_cache ) ) { $directory_cache = array(); } $directory_cache[ $directory ] = $size; // Only write the transient on the top level call and not on recursive calls. if ( $save_cache ) { $expiration = ( wp_using_ext_object_cache() ) ? 0 : 10 * YEAR_IN_SECONDS; set_transient( 'dirsize_cache', $directory_cache, $expiration ); } return $size; } /** * Cleans directory size cache used by recurse_dirsize(). * * Removes the current directory and all parent directories from the `dirsize_cache` transient. * * @since 5.6.0 * @since 5.9.0 Added input validation with a notice for invalid input. * * @param string $path Full path of a directory or file. */ function clean_dirsize_cache( $path ) { if ( ! is_string( $path ) || empty( $path ) ) { wp_trigger_error( '', sprintf( /* translators: 1: Function name, 2: A variable type, like "boolean" or "integer". */ __( '%1$s only accepts a non-empty path string, received %2$s.' ), 'clean_dirsize_cache()', '' . gettype( $path ) . '' ) ); return; } $directory_cache = get_transient( 'dirsize_cache' ); if ( empty( $directory_cache ) ) { return; } $expiration = ( wp_using_ext_object_cache() ) ? 0 : 10 * YEAR_IN_SECONDS; if ( ! str_contains( $path, '/' ) && ! str_contains( $path, '\\' ) ) { unset( $directory_cache[ $path ] ); set_transient( 'dirsize_cache', $directory_cache, $expiration ); return; } $last_path = null; $path = untrailingslashit( $path ); unset( $directory_cache[ $path ] ); while ( $last_path !== $path && DIRECTORY_SEPARATOR !== $path && '.' !== $path && '..' !== $path ) { $last_path = $path; $path = dirname( $path ); unset( $directory_cache[ $path ] ); } set_transient( 'dirsize_cache', $directory_cache, $expiration ); } /** * Checks compatibility with the current WordPress version. * * @since 5.2.0 * * @global string $wp_version The WordPress version string. * * @param string $required Minimum required WordPress version. * @return bool True if required version is compatible or empty, false if not. */ function is_wp_version_compatible( $required ) { global $wp_version; // Strip off any -alpha, -RC, -beta, -src suffixes. list( $version ) = explode( '-', $wp_version ); if ( is_string( $required ) ) { $trimmed = trim( $required ); if ( substr_count( $trimmed, '.' ) > 1 && str_ends_with( $trimmed, '.0' ) ) { $required = substr( $trimmed, 0, -2 ); } } return empty( $required ) || version_compare( $version, $required, '>=' ); } /** * Checks compatibility with the current PHP version. * * @since 5.2.0 * * @param string $required Minimum required PHP version. * @return bool True if required version is compatible or empty, false if not. */ function is_php_version_compatible( $required ) { return empty( $required ) || version_compare( PHP_VERSION, $required, '>=' ); } /** * Checks if two numbers are nearly the same. * * This is similar to using `round()` but the precision is more fine-grained. * * @since 5.3.0 * * @param int|float $expected The expected value. * @param int|float $actual The actual number. * @param int|float $precision Optional. The allowed variation. Default 1. * @return bool Whether the numbers match within the specified precision. */ function wp_fuzzy_number_match( $expected, $actual, $precision = 1 ) { return abs( (float) $expected - (float) $actual ) <= $precision; } /** * Creates and returns the markup for an admin notice. * * @since 6.4.0 * * @param string $message The message. * @param array $args { * Optional. An array of arguments for the admin notice. Default empty array. * * @type string $type Optional. The type of admin notice. * For example, 'error', 'success', 'warning', 'info'. * Default empty string. * @type bool $dismissible Optional. Whether the admin notice is dismissible. Default false. * @type string $id Optional. The value of the admin notice's ID attribute. Default empty string. * @type string[] $additional_classes Optional. A string array of class names. Default empty array. * @type string[] $attributes Optional. Additional attributes for the notice div. Default empty array. * @type bool $paragraph_wrap Optional. Whether to wrap the message in paragraph tags. Default true. * } * @return string The markup for an admin notice. */ function wp_get_admin_notice( $message, $args = array() ) { $defaults = array( 'type' => '', 'dismissible' => false, 'id' => '', 'additional_classes' => array(), 'attributes' => array(), 'paragraph_wrap' => true, ); $args = wp_parse_args( $args, $defaults ); /** * Filters the arguments for an admin notice. * * @since 6.4.0 * * @param array $args The arguments for the admin notice. * @param string $message The message for the admin notice. */ $args = apply_filters( 'wp_admin_notice_args', $args, $message ); $id = ''; $classes = 'notice'; $attributes = ''; if ( is_string( $args['id'] ) ) { $trimmed_id = trim( $args['id'] ); if ( '' !== $trimmed_id ) { $id = 'id="' . $trimmed_id . '" '; } } if ( is_string( $args['type'] ) ) { $type = trim( $args['type'] ); if ( str_contains( $type, ' ' ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: %s: The "type" key. */ __( 'The %s key must be a string without spaces.' ), 'type' ), '6.4.0' ); } if ( '' !== $type ) { $classes .= ' notice-' . $type; } } if ( true === $args['dismissible'] ) { $classes .= ' is-dismissible'; } if ( is_array( $args['additional_classes'] ) && ! empty( $args['additional_classes'] ) ) { $classes .= ' ' . implode( ' ', $args['additional_classes'] ); } if ( is_array( $args['attributes'] ) && ! empty( $args['attributes'] ) ) { $attributes = ''; foreach ( $args['attributes'] as $attr => $val ) { if ( is_bool( $val ) ) { $attributes .= $val ? ' ' . $attr : ''; } elseif ( is_int( $attr ) ) { $attributes .= ' ' . esc_attr( trim( $val ) ); } elseif ( $val ) { $attributes .= ' ' . $attr . '="' . esc_attr( trim( $val ) ) . '"'; } } } if ( false !== $args['paragraph_wrap'] ) { $message = "

$message

"; } $markup = sprintf( '
%4$s
', $id, $classes, $attributes, $message ); /** * Filters the markup for an admin notice. * * @since 6.4.0 * * @param string $markup The HTML markup for the admin notice. * @param string $message The message for the admin notice. * @param array $args The arguments for the admin notice. */ return apply_filters( 'wp_admin_notice_markup', $markup, $message, $args ); } /** * Outputs an admin notice. * * @since 6.4.0 * * @param string $message The message to output. * @param array $args { * Optional. An array of arguments for the admin notice. Default empty array. * * @type string $type Optional. The type of admin notice. * For example, 'error', 'success', 'warning', 'info'. * Default empty string. * @type bool $dismissible Optional. Whether the admin notice is dismissible. Default false. * @type string $id Optional. The value of the admin notice's ID attribute. Default empty string. * @type string[] $additional_classes Optional. A string array of class names. Default empty array. * @type string[] $attributes Optional. Additional attributes for the notice div. Default empty array. * @type bool $paragraph_wrap Optional. Whether to wrap the message in paragraph tags. Default true. * } */ function wp_admin_notice( $message, $args = array() ) { /** * Fires before an admin notice is output. * * @since 6.4.0 * * @param string $message The message for the admin notice. * @param array $args The arguments for the admin notice. */ do_action( 'wp_admin_notice', $message, $args ); echo wp_kses_post( wp_get_admin_notice( $message, $args ) ); } $this->extra_query_vars = & $extra_query_vars; } elseif ( ! empty( $extra_query_vars ) ) { parse_str( $extra_query_vars, $this->extra_query_vars ); } // Process PATH_INFO, REQUEST_URI, and 404 for permalinks. // Fetch the rewrite rules. $rewrite = $wp_rewrite->wp_rewrite_rules(); if ( ! empty( $rewrite ) ) { // If we match a rewrite rule, this will be cleared. $error = '404'; $this->did_permalink = true; $pathinfo = isset( $_SERVER['PATH_INFO'] ) ? $_SERVER['PATH_INFO'] : ''; list( $pathinfo ) = explode( '?', $pathinfo ); $pathinfo = str_replace( '%', '%25', $pathinfo ); list( $req_uri ) = explode( '?', $_SERVER['REQUEST_URI'] ); $self = $_SERVER['PHP_SELF']; $home_path = parse_url( home_url(), PHP_URL_PATH ); $home_path_regex = ''; if ( is_string( $home_path ) && '' !== $home_path ) { $home_path = trim( $home_path, '/' ); $home_path_regex = sprintf( '|^%s|i', preg_quote( $home_path, '|' ) ); } /* * Trim path info from the end and the leading home path from the front. * For path info requests, this leaves us with the requesting filename, if any. * For 404 requests, this leaves us with the requested permalink. */ $req_uri = str_replace( $pathinfo, '', $req_uri ); $req_uri = trim( $req_uri, '/' ); $pathinfo = trim( $pathinfo, '/' ); $self = trim( $self, '/' ); if ( ! empty( $home_path_regex ) ) { $req_uri = preg_replace( $home_path_regex, '', $req_uri ); $req_uri = trim( $req_uri, '/' ); $pathinfo = preg_replace( $home_path_regex, '', $pathinfo ); $pathinfo = trim( $pathinfo, '/' ); $self = preg_replace( $home_path_regex, '', $self ); $self = trim( $self, '/' ); } // The requested permalink is in $pathinfo for path info requests and $req_uri for other requests. if ( ! empty( $pathinfo ) && ! preg_match( '|^.*' . $wp_rewrite->index . '$|', $pathinfo ) ) { $requested_path = $pathinfo; } else { // If the request uri is the index, blank it out so that we don't try to match it against a rule. if ( $req_uri === $wp_rewrite->index ) { $req_uri = ''; } $requested_path = $req_uri; } $requested_file = $req_uri; $this->request = $requested_path; // Look for matches. $request_match = $requested_path; if ( empty( $request_match ) ) { // An empty request could only match against ^$ regex. if ( isset( $rewrite['$'] ) ) { $this->matched_rule = '$'; $query = $rewrite['$']; $matches = array( '' ); } } else { foreach ( (array) $rewrite as $match => $query ) { // If the requested file is the anchor of the match, prepend it to the path info. if ( ! empty( $requested_file ) && str_starts_with( $match, $requested_file ) && $requested_file !== $requested_path ) { $request_match = $requested_file . '/' . $requested_path; } if ( preg_match( "#^$match#", $request_match, $matches ) || preg_match( "#^$match#", urldecode( $request_match ), $matches ) ) { if ( $wp_rewrite->use_verbose_page_rules && preg_match( '/pagename=\$matches\[([0-9]+)\]/', $query, $varmatch ) ) { // This is a verbose page match, let's check to be sure about it. $page = get_page_by_path( $matches[ $varmatch[1] ] ); if ( ! $page ) { continue; } $post_status_obj = get_post_status_object( $page->post_status ); if ( ! $post_status_obj->public && ! $post_status_obj->protected && ! $post_status_obj->private && $post_status_obj->exclude_from_search ) { continue; } } // Got a match. $this->matched_rule = $match; break; } } } if ( ! empty( $this->matched_rule ) ) { // Trim the query of everything up to the '?'. $query = preg_replace( '!^.+\?!', '', $query ); // Substitute the substring matches into the query. $query = addslashes( WP_MatchesMapRegex::apply( $query, $matches ) ); $this->matched_query = $query; // Parse the query. parse_str( $query, $perma_query_vars ); // If we're processing a 404 request, clear the error var since we found something. if ( '404' === $error ) { unset( $error, $_GET['error'] ); } } // If req_uri is empty or if it is a request for ourself, unset error. if ( empty( $requested_path ) || $requested_file === $self || str_contains( $_SERVER['PHP_SELF'], 'wp-admin/' ) ) { unset( $error, $_GET['error'] ); if ( isset( $perma_query_vars ) && str_contains( $_SERVER['PHP_SELF'], 'wp-admin/' ) ) { unset( $perma_query_vars ); } $this->did_permalink = false; } } /** * Filters the query variables allowed before processing. * * Allows (publicly allowed) query vars to be added, removed, or changed prior * to executing the query. Needed to allow custom rewrite rules using your own arguments * to work, or any other custom query variables you want to be publicly available. * * @since 1.5.0 * * @param string[] $public_query_vars The array of allowed query variable names. */ $this->public_query_vars = apply_filters( 'query_vars', $this->public_query_vars ); foreach ( get_post_types( array(), 'objects' ) as $post_type => $t ) { if ( is_post_type_viewable( $t ) && $t->query_var ) { $post_type_query_vars[ $t->query_var ] = $post_type; } } foreach ( $this->public_query_vars as $wpvar ) { if ( isset( $this->extra_query_vars[ $wpvar ] ) ) { $this->query_vars[ $wpvar ] = $this->extra_query_vars[ $wpvar ]; } elseif ( isset( $_GET[ $wpvar ] ) && isset( $_POST[ $wpvar ] ) && $_GET[ $wpvar ] !== $_POST[ $wpvar ] ) { wp_die( __( 'A variable mismatch has been detected.' ), __( 'Sorry, you are not allowed to view this item.' ), 400 ); } elseif ( isset( $_POST[ $wpvar ] ) ) { $this->query_vars[ $wpvar ] = $_POST[ $wpvar ]; } elseif ( isset( $_GET[ $wpvar ] ) ) { $this->query_vars[ $wpvar ] = $_GET[ $wpvar ]; } elseif ( isset( $perma_query_vars[ $wpvar ] ) ) { $this->query_vars[ $wpvar ] = $perma_query_vars[ $wpvar ]; } if ( ! empty( $this->query_vars[ $wpvar ] ) ) { if ( ! is_array( $this->query_vars[ $wpvar ] ) ) { $this->query_vars[ $wpvar ] = (string) $this->query_vars[ $wpvar ]; } else { foreach ( $this->query_vars[ $wpvar ] as $vkey => $v ) { if ( is_scalar( $v ) ) { $this->query_vars[ $wpvar ][ $vkey ] = (string) $v; } } } if ( isset( $post_type_query_vars[ $wpvar ] ) ) { $this->query_vars['post_type'] = $post_type_query_vars[ $wpvar ]; $this->query_vars['name'] = $this->query_vars[ $wpvar ]; } } } // Convert urldecoded spaces back into '+'. foreach ( get_taxonomies( array(), 'objects' ) as $taxonomy => $t ) { if ( $t->query_var && isset( $this->query_vars[ $t->query_var ] ) ) { $this->query_vars[ $t->query_var ] = str_replace( ' ', '+', $this->query_vars[ $t->query_var ] ); } } // Don't allow non-publicly queryable taxonomies to be queried from the front end. if ( ! is_admin() ) { foreach ( get_taxonomies( array( 'publicly_queryable' => false ), 'objects' ) as $taxonomy => $t ) { /* * Disallow when set to the 'taxonomy' query var. * Non-publicly queryable taxonomies cannot register custom query vars. See register_taxonomy(). */ if ( isset( $this->query_vars['taxonomy'] ) && $taxonomy === $this->query_vars['taxonomy'] ) { unset( $this->query_vars['taxonomy'], $this->query_vars['term'] ); } } } // Limit publicly queried post_types to those that are 'publicly_queryable'. if ( isset( $this->query_vars['post_type'] ) ) { $queryable_post_types = get_post_types( array( 'publicly_queryable' => true ) ); if ( ! is_array( $this->query_vars['post_type'] ) ) { if ( ! in_array( $this->query_vars['post_type'], $queryable_post_types, true ) ) { unset( $this->query_vars['post_type'] ); } } else { $this->query_vars['post_type'] = array_intersect( $this->query_vars['post_type'], $queryable_post_types ); } } // Resolve conflicts between posts with numeric slugs and date archive queries. $this->query_vars = wp_resolve_numeric_slug_conflicts( $this->query_vars ); foreach ( (array) $this->private_query_vars as $var ) { if ( isset( $this->extra_query_vars[ $var ] ) ) { $this->query_vars[ $var ] = $this->extra_query_vars[ $var ]; } } if ( isset( $error ) ) { $this->query_vars['error'] = $error; } /** * Filters the array of parsed query variables. * * @since 2.1.0 * * @param array $query_vars The array of requested query variables. */ $this->query_vars = apply_filters( 'request', $this->query_vars ); /** * Fires once all query variables for the current request have been parsed. * * @since 2.1.0 * * @param WP $wp Current WordPress environment instance (passed by reference). */ do_action_ref_array( 'parse_request', array( &$this ) ); return true; } /** * Sends additional HTTP headers for caching, content type, etc. * * Sets the Content-Type header. Sets the 'error' status (if passed) and optionally exits. * If showing a feed, it will also send Last-Modified, ETag, and 304 status if needed. * * @since 2.0.0 * @since 4.4.0 `X-Pingback` header is added conditionally for single posts that allow pings. * @since 6.1.0 Runs after posts have been queried. * * @global WP_Query $wp_query WordPress Query object. */ public function send_headers() { global $wp_query; $headers = array(); $status = null; $exit_required = false; $date_format = 'D, d M Y H:i:s'; if ( is_user_logged_in() ) { $headers = array_merge( $headers, wp_get_nocache_headers() ); } elseif ( ! empty( $_GET['unapproved'] ) && ! empty( $_GET['moderation-hash'] ) ) { // Unmoderated comments are only visible for 10 minutes via the moderation hash. $expires = 10 * MINUTE_IN_SECONDS; $headers['Expires'] = gmdate( $date_format, time() + $expires ); $headers['Cache-Control'] = sprintf( 'max-age=%d, must-revalidate', $expires ); } if ( ! empty( $this->query_vars['error'] ) ) { $status = (int) $this->query_vars['error']; if ( 404 === $status ) { if ( ! is_user_logged_in() ) { $headers = array_merge( $headers, wp_get_nocache_headers() ); } $headers['Content-Type'] = get_option( 'html_type' ) . '; charset=' . get_option( 'blog_charset' ); } elseif ( in_array( $status, array( 403, 500, 502, 503 ), true ) ) { $exit_required = true; } } elseif ( empty( $this->query_vars['feed'] ) ) { $headers['Content-Type'] = get_option( 'html_type' ) . '; charset=' . get_option( 'blog_charset' ); } else { // Set the correct content type for feeds. $type = $this->query_vars['feed']; if ( 'feed' === $this->query_vars['feed'] ) { $type = get_default_feed(); } $headers['Content-Type'] = feed_content_type( $type ) . '; charset=' . get_option( 'blog_charset' ); // We're showing a feed, so WP is indeed the only thing that last changed. if ( ! empty( $this->query_vars['withcomments'] ) || str_contains( $this->query_vars['feed'], 'comments-' ) || ( empty( $this->query_vars['withoutcomments'] ) && ( ! empty( $this->query_vars['p'] ) || ! empty( $this->query_vars['name'] ) || ! empty( $this->query_vars['page_id'] ) || ! empty( $this->query_vars['pagename'] ) || ! empty( $this->query_vars['attachment'] ) || ! empty( $this->query_vars['attachment_id'] ) ) ) ) { $wp_last_modified_post = mysql2date( $date_format, get_lastpostmodified( 'GMT' ), false ); $wp_last_modified_comment = mysql2date( $date_format, get_lastcommentmodified( 'GMT' ), false ); if ( strtotime( $wp_last_modified_post ) > strtotime( $wp_last_modified_comment ) ) { $wp_last_modified = $wp_last_modified_post; } else { $wp_last_modified = $wp_last_modified_comment; } } else { $wp_last_modified = mysql2date( $date_format, get_lastpostmodified( 'GMT' ), false ); } if ( ! $wp_last_modified ) { $wp_last_modified = gmdate( $date_format ); } $wp_last_modified .= ' GMT'; $wp_etag = '"' . md5( $wp_last_modified ) . '"'; $headers['Last-Modified'] = $wp_last_modified; $headers['ETag'] = $wp_etag; // Support for conditional GET. if ( isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ) { $client_etag = wp_unslash( $_SERVER['HTTP_IF_NONE_MATCH'] ); } else { $client_etag = ''; } if ( isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) { $client_last_modified = trim( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ); } else { $client_last_modified = ''; } // If string is empty, return 0. If not, attempt to parse into a timestamp. $client_modified_timestamp = $client_last_modified ? strtotime( $client_last_modified ) : 0; // Make a timestamp for our most recent modification. $wp_modified_timestamp = strtotime( $wp_last_modified ); if ( ( $client_last_modified && $client_etag ) ? ( ( $client_modified_timestamp >= $wp_modified_timestamp ) && ( $client_etag === $wp_etag ) ) : ( ( $client_modified_timestamp >= $wp_modified_timestamp ) || ( $client_etag === $wp_etag ) ) ) { $status = 304; $exit_required = true; } } if ( is_singular() ) { $post = isset( $wp_query->post ) ? $wp_query->post : null; // Only set X-Pingback for single posts that allow pings. if ( $post && pings_open( $post ) ) { $headers['X-Pingback'] = get_bloginfo( 'pingback_url', 'display' ); } } /** * Filters the HTTP headers before they're sent to the browser. * * @since 2.8.0 * * @param string[] $headers Associative array of headers to be sent. * @param WP $wp Current WordPress environment instance. */ $headers = apply_filters( 'wp_headers', $headers, $this ); if ( ! empty( $status ) ) { status_header( $status ); } // If Last-Modified is set to false, it should not be sent (no-cache situation). if ( isset( $headers['Last-Modified'] ) && false === $headers['Last-Modified'] ) { unset( $headers['Last-Modified'] ); if ( ! headers_sent() ) { header_remove( 'Last-Modified' ); } } if ( ! headers_sent() ) { foreach ( (array) $headers as $name => $field_value ) { header( "{$name}: {$field_value}" ); } } if ( $exit_required ) { exit; } /** * Fires once the requested HTTP headers for caching, content type, etc. have been sent. * * @since 2.1.0 * * @param WP $wp Current WordPress environment instance (passed by reference). */ do_action_ref_array( 'send_headers', array( &$this ) ); } /** * Sets the query string property based off of the query variable property. * * The {@see 'query_string'} filter is deprecated, but still works. Plugins should * use the {@see 'request'} filter instead. * * @since 2.0.0 */ public function build_query_string() { $this->query_string = ''; foreach ( (array) array_keys( $this->query_vars ) as $wpvar ) { if ( '' !== $this->query_vars[ $wpvar ] ) { $this->query_string .= ( strlen( $this->query_string ) < 1 ) ? '' : '&'; if ( ! is_scalar( $this->query_vars[ $wpvar ] ) ) { // Discard non-scalars. continue; } $this->query_string .= $wpvar . '=' . rawurlencode( $this->query_vars[ $wpvar ] ); } } if ( has_filter( 'query_string' ) ) { // Don't bother filtering and parsing if no plugins are hooked in. /** * Filters the query string before parsing. * * @since 1.5.0 * @deprecated 2.1.0 Use {@see 'query_vars'} or {@see 'request'} filters instead. * * @param string $query_string The query string to modify. */ $this->query_string = apply_filters_deprecated( 'query_string', array( $this->query_string ), '2.1.0', 'query_vars, request' ); parse_str( $this->query_string, $this->query_vars ); } } /** * Set up the WordPress Globals. * * The query_vars property will be extracted to the GLOBALS. So care should * be taken when naming global variables that might interfere with the * WordPress environment. * * @since 2.0.0 * * @global WP_Query $wp_query WordPress Query object. * @global string $query_string Query string for the loop. * @global array $posts The found posts. * @global WP_Post|null $post The current post, if available. * @global string $request The SQL statement for the request. * @global int $more Only set, if single page or post. * @global int $single If single page or post. Only set, if single page or post. * @global WP_User $authordata Only set, if author archive. */ public function register_globals() { global $wp_query; // Extract updated query vars back into global namespace. foreach ( (array) $wp_query->query_vars as $key => $value ) { $GLOBALS[ $key ] = $value; } $GLOBALS['query_string'] = $this->query_string; $GLOBALS['posts'] = & $wp_query->posts; $GLOBALS['post'] = isset( $wp_query->post ) ? $wp_query->post : null; $GLOBALS['request'] = $wp_query->request; if ( $wp_query->is_single() || $wp_query->is_page() ) { $GLOBALS['more'] = 1; $GLOBALS['single'] = 1; } if ( $wp_query->is_author() ) { $GLOBALS['authordata'] = get_userdata( get_queried_object_id() ); } } /** * Set up the current user. * * @since 2.0.0 */ public function init() { wp_get_current_user(); } /** * Set up the Loop based on the query variables. * * @since 2.0.0 * * @global WP_Query $wp_the_query WordPress Query object. */ public function query_posts() { global $wp_the_query; $this->build_query_string(); $wp_the_query->query( $this->query_vars ); } /** * Set the Headers for 404, if nothing is found for requested URL. * * Issue a 404 if a request doesn't match any posts and doesn't match any object * (e.g. an existing-but-empty category, tag, author) and a 404 was not already issued, * and if the request was not a search or the homepage. * * Otherwise, issue a 200. * * This sets headers after posts have been queried. handle_404() really means "handle status". * By inspecting the result of querying posts, seemingly successful requests can be switched to * a 404 so that canonical redirection logic can kick in. * * @since 2.0.0 * * @global WP_Query $wp_query WordPress Query object. */ public function handle_404() { global $wp_query; /** * Filters whether to short-circuit default header status handling. * * Returning a non-false value from the filter will short-circuit the handling * and return early. * * @since 4.5.0 * * @param bool $preempt Whether to short-circuit default header status handling. Default false. * @param WP_Query $wp_query WordPress Query object. */ if ( false !== apply_filters( 'pre_handle_404', false, $wp_query ) ) { return; } // If we've already issued a 404, bail. if ( is_404() ) { return; } $set_404 = true; // Never 404 for the admin, robots, or favicon. if ( is_admin() || is_robots() || is_favicon() ) { $set_404 = false; // If posts were found, check for paged content. } elseif ( $wp_query->posts ) { $content_found = true; if ( is_singular() ) { $post = isset( $wp_query->post ) ? $wp_query->post : null; $next = ''; // Check for paged content that exceeds the max number of pages. if ( $post && ! empty( $this->query_vars['page'] ) ) { // Check if content is actually intended to be paged. if ( str_contains( $post->post_content, $next ) ) { $page = trim( $this->query_vars['page'], '/' ); $content_found = (int) $page <= ( substr_count( $post->post_content, $next ) + 1 ); } else { $content_found = false; } } } // The posts page does not support the pagination. if ( $wp_query->is_posts_page && ! empty( $this->query_vars['page'] ) ) { $content_found = false; } if ( $content_found ) { $set_404 = false; } // We will 404 for paged queries, as no posts were found. } elseif ( ! is_paged() ) { $author = get_query_var( 'author' ); // Don't 404 for authors without posts as long as they matched an author on this site. if ( is_author() && is_numeric( $author ) && $author > 0 && is_user_member_of_blog( $author ) // Don't 404 for these queries if they matched an object. || ( is_tag() || is_category() || is_tax() || is_post_type_archive() ) && get_queried_object() // Don't 404 for these queries either. || is_home() || is_search() || is_feed() ) { $set_404 = false; } } if ( $set_404 ) { // Guess it's time to 404. $wp_query->set_404(); status_header( 404 ); nocache_headers(); } else { status_header( 200 ); } } /** * Sets up all of the variables required by the WordPress environment. * * The action {@see 'wp'} has one parameter that references the WP object. It * allows for accessing the properties and methods to further manipulate the * object. * * @since 2.0.0 * * @param string|array $query_args Passed to parse_request(). */ public function main( $query_args = '' ) { $this->init(); $parsed = $this->parse_request( $query_args ); if ( $parsed ) { $this->query_posts(); $this->handle_404(); $this->register_globals(); } $this->send_headers(); /** * Fires once the WordPress environment has been set up. * * @since 2.1.0 * * @param WP $wp Current WordPress environment instance (passed by reference). */ do_action_ref_array( 'wp', array( &$this ) ); } }