ssed by reference). */ function update_post_cache( &$posts ) { if ( ! $posts ) { return; } $data = array(); foreach ( $posts as $post ) { if ( empty( $post->filter ) || 'raw' !== $post->filter ) { $post = sanitize_post( $post, 'raw' ); } $data[ $post->ID ] = $post; } wp_cache_add_multiple( $data, 'posts' ); } /** * Will clean the post in the cache. * * Cleaning means delete from the cache of the post. Will call to clean the term * object cache associated with the post ID. * * This function not run if $_wp_suspend_cache_invalidation is not empty. See * wp_suspend_cache_invalidation(). * * @since 2.0.0 * * @global bool $_wp_suspend_cache_invalidation * * @param int|WP_Post $post Post ID or post object to remove from the cache. */ function clean_post_cache( $post ) { global $_wp_suspend_cache_invalidation; if ( ! empty( $_wp_suspend_cache_invalidation ) ) { return; } $post = get_post( $post ); if ( ! $post ) { return; } wp_cache_delete( $post->ID, 'posts' ); wp_cache_delete( 'post_parent:' . (string) $post->ID, 'posts' ); wp_cache_delete( $post->ID, 'post_meta' ); clean_object_term_cache( $post->ID, $post->post_type ); wp_cache_delete( 'wp_get_archives', 'general' ); /** * Fires immediately after the given post's cache is cleaned. * * @since 2.5.0 * * @param int $post_id Post ID. * @param WP_Post $post Post object. */ do_action( 'clean_post_cache', $post->ID, $post ); if ( 'page' === $post->post_type ) { wp_cache_delete( 'all_page_ids', 'posts' ); /** * Fires immediately after the given page's cache is cleaned. * * @since 2.5.0 * * @param int $post_id Post ID. */ do_action( 'clean_page_cache', $post->ID ); } wp_cache_set_posts_last_changed(); } /** * Updates post, term, and metadata caches for a list of post objects. * * @since 1.5.0 * * @param WP_Post[] $posts Array of post objects (passed by reference). * @param string $post_type Optional. Post type. Default 'post'. * @param bool $update_term_cache Optional. Whether to update the term cache. Default true. * @param bool $update_meta_cache Optional. Whether to update the meta cache. Default true. */ function update_post_caches( &$posts, $post_type = 'post', $update_term_cache = true, $update_meta_cache = true ) { // No point in doing all this work if we didn't match any posts. if ( ! $posts ) { return; } update_post_cache( $posts ); $post_ids = array(); foreach ( $posts as $post ) { $post_ids[] = $post->ID; } if ( ! $post_type ) { $post_type = 'any'; } if ( $update_term_cache ) { if ( is_array( $post_type ) ) { $ptypes = $post_type; } elseif ( 'any' === $post_type ) { $ptypes = array(); // Just use the post_types in the supplied posts. foreach ( $posts as $post ) { $ptypes[] = $post->post_type; } $ptypes = array_unique( $ptypes ); } else { $ptypes = array( $post_type ); } if ( ! empty( $ptypes ) ) { update_object_term_cache( $post_ids, $ptypes ); } } if ( $update_meta_cache ) { update_postmeta_cache( $post_ids ); } } /** * Updates post author user caches for a list of post objects. * * @since 6.1.0 * * @param WP_Post[] $posts Array of post objects. */ function update_post_author_caches( $posts ) { /* * cache_users() is a pluggable function so is not available prior * to the `plugins_loaded` hook firing. This is to ensure against * fatal errors when the function is not available. */ if ( ! function_exists( 'cache_users' ) ) { return; } $author_ids = wp_list_pluck( $posts, 'post_author' ); $author_ids = array_map( 'absint', $author_ids ); $author_ids = array_unique( array_filter( $author_ids ) ); cache_users( $author_ids ); } /** * Updates parent post caches for a list of post objects. * * @since 6.1.0 * * @param WP_Post[] $posts Array of post objects. */ function update_post_parent_caches( $posts ) { $parent_ids = wp_list_pluck( $posts, 'post_parent' ); $parent_ids = array_map( 'absint', $parent_ids ); $parent_ids = array_unique( array_filter( $parent_ids ) ); if ( ! empty( $parent_ids ) ) { _prime_post_caches( $parent_ids, false ); } } /** * Updates metadata cache for a list of post IDs. * * Performs SQL query to retrieve the metadata for the post IDs and updates the * metadata cache for the posts. Therefore, the functions, which call this * function, do not need to perform SQL queries on their own. * * @since 2.1.0 * * @param int[] $post_ids Array of post IDs. * @return array|false An array of metadata on success, false if there is nothing to update. */ function update_postmeta_cache( $post_ids ) { return update_meta_cache( 'post', $post_ids ); } /** * Will clean the attachment in the cache. * * Cleaning means delete from the cache. Optionally will clean the term * object cache associated with the attachment ID. * * This function will not run if $_wp_suspend_cache_invalidation is not empty. * * @since 3.0.0 * * @global bool $_wp_suspend_cache_invalidation * * @param int $id The attachment ID in the cache to clean. * @param bool $clean_terms Optional. Whether to clean terms cache. Default false. */ function clean_attachment_cache( $id, $clean_terms = false ) { global $_wp_suspend_cache_invalidation; if ( ! empty( $_wp_suspend_cache_invalidation ) ) { return; } $id = (int) $id; wp_cache_delete( $id, 'posts' ); wp_cache_delete( $id, 'post_meta' ); if ( $clean_terms ) { clean_object_term_cache( $id, 'attachment' ); } /** * Fires after the given attachment's cache is cleaned. * * @since 3.0.0 * * @param int $id Attachment ID. */ do_action( 'clean_attachment_cache', $id ); } // // Hooks. // /** * Hook for managing future post transitions to published. * * @since 2.3.0 * @access private * * @see wp_clear_scheduled_hook() * @global wpdb $wpdb WordPress database abstraction object. * * @param string $new_status New post status. * @param string $old_status Previous post status. * @param WP_Post $post Post object. */ function _transition_post_status( $new_status, $old_status, $post ) { global $wpdb; if ( 'publish' !== $old_status && 'publish' === $new_status ) { // Reset GUID if transitioning to publish and it is empty. if ( '' === get_the_guid( $post->ID ) ) { $wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post->ID ) ), array( 'ID' => $post->ID ) ); } /** * Fires when a post's status is transitioned from private to published. * * @since 1.5.0 * @deprecated 2.3.0 Use {@see 'private_to_publish'} instead. * * @param int $post_id Post ID. */ do_action_deprecated( 'private_to_published', array( $post->ID ), '2.3.0', 'private_to_publish' ); } // If published posts changed clear the lastpostmodified cache. if ( 'publish' === $new_status || 'publish' === $old_status ) { foreach ( array( 'server', 'gmt', 'blog' ) as $timezone ) { wp_cache_delete( "lastpostmodified:$timezone", 'timeinfo' ); wp_cache_delete( "lastpostdate:$timezone", 'timeinfo' ); wp_cache_delete( "lastpostdate:$timezone:{$post->post_type}", 'timeinfo' ); } } if ( $new_status !== $old_status ) { wp_cache_delete( _count_posts_cache_key( $post->post_type ), 'counts' ); wp_cache_delete( _count_posts_cache_key( $post->post_type, 'readable' ), 'counts' ); } // Always clears the hook in case the post status bounced from future to draft. wp_clear_scheduled_hook( 'publish_future_post', array( $post->ID ) ); } /** * Hook used to schedule publication for a post marked for the future. * * The $post properties used and must exist are 'ID' and 'post_date_gmt'. * * @since 2.3.0 * @access private * * @param int $deprecated Not used. Can be set to null. Never implemented. Not marked * as deprecated with _deprecated_argument() as it conflicts with * wp_transition_post_status() and the default filter for _future_post_hook(). * @param WP_Post $post Post object. */ function _future_post_hook( $deprecated, $post ) { wp_clear_scheduled_hook( 'publish_future_post', array( $post->ID ) ); wp_schedule_single_event( strtotime( get_gmt_from_date( $post->post_date ) . ' GMT' ), 'publish_future_post', array( $post->ID ) ); } /** * Hook to schedule pings and enclosures when a post is published. * * Uses XMLRPC_REQUEST and WP_IMPORTING constants. * * @since 2.3.0 * @access private * * @param int $post_id The ID of the post being published. */ function _publish_post_hook( $post_id ) { if ( defined( 'XMLRPC_REQUEST' ) ) { /** * Fires when _publish_post_hook() is called during an XML-RPC request. * * @since 2.1.0 * * @param int $post_id Post ID. */ do_action( 'xmlrpc_publish_post', $post_id ); } if ( defined( 'WP_IMPORTING' ) ) { return; } if ( get_option( 'default_pingback_flag' ) ) { add_post_meta( $post_id, '_pingme', '1', true ); } add_post_meta( $post_id, '_encloseme', '1', true ); $to_ping = get_to_ping( $post_id ); if ( ! empty( $to_ping ) ) { add_post_meta( $post_id, '_trackbackme', '1' ); } if ( ! wp_next_scheduled( 'do_pings' ) ) { wp_schedule_single_event( time(), 'do_pings' ); } } /** * Returns the ID of the post's parent. * * @since 3.1.0 * @since 5.9.0 The `$post` parameter was made optional. * * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post. * @return int|false Post parent ID (which can be 0 if there is no parent), * or false if the post does not exist. */ function wp_get_post_parent_id( $post = null ) { $post = get_post( $post ); if ( ! $post || is_wp_error( $post ) ) { return false; } return (int) $post->post_parent; } /** * Checks the given subset of the post hierarchy for hierarchy loops. * * Prevents loops from forming and breaks those that it finds. Attached * to the {@see 'wp_insert_post_parent'} filter. * * @since 3.1.0 * * @see wp_find_hierarchy_loop() * * @param int $post_parent ID of the parent for the post we're checking. * @param int $post_id ID of the post we're checking. * @return int The new post_parent for the post, 0 otherwise. */ function wp_check_post_hierarchy_for_loops( $post_parent, $post_id ) { // Nothing fancy here - bail. if ( ! $post_parent ) { return 0; } // New post can't cause a loop. if ( ! $post_id ) { return $post_parent; } // Can't be its own parent. if ( $post_parent == $post_id ) { return 0; } // Now look for larger loops. $loop = wp_find_hierarchy_loop( 'wp_get_post_parent_id', $post_id, $post_parent ); if ( ! $loop ) { return $post_parent; // No loop. } // Setting $post_parent to the given value causes a loop. if ( isset( $loop[ $post_id ] ) ) { return 0; } // There's a loop, but it doesn't contain $post_id. Break the loop. foreach ( array_keys( $loop ) as $loop_member ) { wp_update_post( array( 'ID' => $loop_member, 'post_parent' => 0, ) ); } return $post_parent; } /** * Sets the post thumbnail (featured image) for the given post. * * @since 3.1.0 * * @param int|WP_Post $post Post ID or post object where thumbnail should be attached. * @param int $thumbnail_id Thumbnail to attach. * @return int|bool True on success, false on failure. */ function set_post_thumbnail( $post, $thumbnail_id ) { $post = get_post( $post ); $thumbnail_id = absint( $thumbnail_id ); if ( $post && $thumbnail_id && get_post( $thumbnail_id ) ) { if ( wp_get_attachment_image( $thumbnail_id, 'thumbnail' ) ) { return update_post_meta( $post->ID, '_thumbnail_id', $thumbnail_id ); } else { return delete_post_meta( $post->ID, '_thumbnail_id' ); } } return false; } /** * Removes the thumbnail (featured image) from the given post. * * @since 3.3.0 * * @param int|WP_Post $post Post ID or post object from which the thumbnail should be removed. * @return bool True on success, false on failure. */ function delete_post_thumbnail( $post ) { $post = get_post( $post ); if ( $post ) { return delete_post_meta( $post->ID, '_thumbnail_id' ); } return false; } /** * Deletes auto-drafts for new posts that are > 7 days old. * * @since 3.4.0 * * @global wpdb $wpdb WordPress database abstraction object. */ function wp_delete_auto_drafts() { global $wpdb; // Cleanup old auto-drafts more than 7 days old. $old_posts = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_status = 'auto-draft' AND DATE_SUB( NOW(), INTERVAL 7 DAY ) > post_date" ); foreach ( (array) $old_posts as $delete ) { // Force delete. wp_delete_post( $delete, true ); } } /** * Queues posts for lazy-loading of term meta. * * @since 4.5.0 * * @param WP_Post[] $posts Array of WP_Post objects. */ function wp_queue_posts_for_term_meta_lazyload( $posts ) { $post_type_taxonomies = array(); $prime_post_terms = array(); foreach ( $posts as $post ) { if ( ! ( $post instanceof WP_Post ) ) { continue; } if ( ! isset( $post_type_taxonomies[ $post->post_type ] ) ) { $post_type_taxonomies[ $post->post_type ] = get_object_taxonomies( $post->post_type ); } foreach ( $post_type_taxonomies[ $post->post_type ] as $taxonomy ) { $prime_post_terms[ $taxonomy ][] = $post->ID; } } $term_ids = array(); if ( $prime_post_terms ) { foreach ( $prime_post_terms as $taxonomy => $post_ids ) { $cached_term_ids = wp_cache_get_multiple( $post_ids, "{$taxonomy}_relationships" ); if ( is_array( $cached_term_ids ) ) { $cached_term_ids = array_filter( $cached_term_ids ); foreach ( $cached_term_ids as $_term_ids ) { // Backward compatibility for if a plugin is putting objects into the cache, rather than IDs. foreach ( $_term_ids as $term_id ) { if ( is_numeric( $term_id ) ) { $term_ids[] = (int) $term_id; } elseif ( isset( $term_id->term_id ) ) { $term_ids[] = (int) $term_id->term_id; } } } } } $term_ids = array_unique( $term_ids ); } wp_lazyload_term_meta( $term_ids ); } /** * Updates the custom taxonomies' term counts when a post's status is changed. * * For example, default posts term counts (for custom taxonomies) don't include * private / draft posts. * * @since 3.3.0 * @access private * * @param string $new_status New post status. * @param string $old_status Old post status. * @param WP_Post $post Post object. */ function _update_term_count_on_transition_post_status( $new_status, $old_status, $post ) { // Update counts for the post's terms. foreach ( (array) get_object_taxonomies( $post->post_type ) as $taxonomy ) { $tt_ids = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'tt_ids' ) ); wp_update_term_count( $tt_ids, $taxonomy ); } } /** * Adds any posts from the given IDs to the cache that do not already exist in cache. * * @since 3.4.0 * @since 6.1.0 This function is no longer marked as "private". * * @see update_post_cache() * @see update_postmeta_cache() * @see update_object_term_cache() * * @global wpdb $wpdb WordPress database abstraction object. * * @param int[] $ids ID list. * @param bool $update_term_cache Optional. Whether to update the term cache. Default true. * @param bool $update_meta_cache Optional. Whether to update the meta cache. Default true. */ function _prime_post_caches( $ids, $update_term_cache = true, $update_meta_cache = true ) { global $wpdb; $non_cached_ids = _get_non_cached_ids( $ids, 'posts' ); if ( ! empty( $non_cached_ids ) ) { $fresh_posts = $wpdb->get_results( sprintf( "SELECT $wpdb->posts.* FROM $wpdb->posts WHERE ID IN (%s)", implode( ',', $non_cached_ids ) ) ); if ( $fresh_posts ) { // Despite the name, update_post_cache() expects an array rather than a single post. update_post_cache( $fresh_posts ); } } if ( $update_meta_cache ) { update_postmeta_cache( $ids ); } if ( $update_term_cache ) { $post_types = array_map( 'get_post_type', $ids ); $post_types = array_unique( $post_types ); update_object_term_cache( $ids, $post_types ); } } /** * Prime the cache containing the parent ID of various post objects. * * @since 6.4.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int[] $ids ID list. */ function _prime_post_parent_id_caches( array $ids ) { global $wpdb; $ids = array_filter( $ids, '_validate_cache_id' ); $ids = array_unique( array_map( 'intval', $ids ), SORT_NUMERIC ); if ( empty( $ids ) ) { return; } $cache_keys = array(); foreach ( $ids as $id ) { $cache_keys[ $id ] = 'post_parent:' . (string) $id; } $cached_data = wp_cache_get_multiple( array_values( $cache_keys ), 'posts' ); $non_cached_ids = array(); foreach ( $cache_keys as $id => $cache_key ) { if ( false === $cached_data[ $cache_key ] ) { $non_cached_ids[] = $id; } } if ( ! empty( $non_cached_ids ) ) { $fresh_posts = $wpdb->get_results( sprintf( "SELECT $wpdb->posts.ID, $wpdb->posts.post_parent FROM $wpdb->posts WHERE ID IN (%s)", implode( ',', $non_cached_ids ) ) ); if ( $fresh_posts ) { $post_parent_data = array(); foreach ( $fresh_posts as $fresh_post ) { $post_parent_data[ 'post_parent:' . (string) $fresh_post->ID ] = (int) $fresh_post->post_parent; } wp_cache_add_multiple( $post_parent_data, 'posts' ); } } } /** * Adds a suffix if any trashed posts have a given slug. * * Store its desired (i.e. current) slug so it can try to reclaim it * if the post is untrashed. * * For internal use. * * @since 4.5.0 * @access private * * @param string $post_name Post slug. * @param int $post_id Optional. Post ID that should be ignored. Default 0. */ function wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_id = 0 ) { $trashed_posts_with_desired_slug = get_posts( array( 'name' => $post_name, 'post_status' => 'trash', 'post_type' => 'any', 'nopaging' => true, 'post__not_in' => array( $post_id ), ) ); if ( ! empty( $trashed_posts_with_desired_slug ) ) { foreach ( $trashed_posts_with_desired_slug as $_post ) { wp_add_trashed_suffix_to_post_name_for_post( $_post ); } } } /** * Adds a trashed suffix for a given post. * * Store its desired (i.e. current) slug so it can try to reclaim it * if the post is untrashed. * * For internal use. * * @since 4.5.0 * @access private * * @global wpdb $wpdb WordPress database abstraction object. * * @param WP_Post $post The post. * @return string New slug for the post. */ function wp_add_trashed_suffix_to_post_name_for_post( $post ) { global $wpdb; $post = get_post( $post ); if ( str_ends_with( $post->post_name, '__trashed' ) ) { return $post->post_name; } add_post_meta( $post->ID, '_wp_desired_post_slug', $post->post_name ); $post_name = _truncate_post_slug( $post->post_name, 191 ) . '__trashed'; $wpdb->update( $wpdb->posts, array( 'post_name' => $post_name ), array( 'ID' => $post->ID ) ); clean_post_cache( $post->ID ); return $post_name; } /** * Sets the last changed time for the 'posts' cache group. * * @since 5.0.0 */ function wp_cache_set_posts_last_changed() { wp_cache_set_last_changed( 'posts' ); } /** * Gets all available post MIME types for a given post type. * * @since 2.5.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $type * @return string[] An array of MIME types. */ function get_available_post_mime_types( $type = 'attachment' ) { global $wpdb; /** * Filters the list of available post MIME types for the given post type. * * @since 6.4.0 * * @param string[]|null $mime_types An array of MIME types. Default null. * @param string $type The post type name. Usually 'attachment' but can be any post type. */ $mime_types = apply_filters( 'pre_get_available_post_mime_types', null, $type ); if ( ! is_array( $mime_types ) ) { $mime_types = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT post_mime_type FROM $wpdb->posts WHERE post_type = %s AND post_mime_type != ''", $type ) ); } // Remove nulls from returned $mime_types. return array_values( array_filter( $mime_types ) ); } /** * Retrieves the path to an uploaded image file. * * Similar to `get_attached_file()` however some images may have been processed after uploading * to make them suitable for web use. In this case the attached "full" size file is usually replaced * with a scaled down version of the original image. This function always returns the path * to the originally uploaded image file. * * @since 5.3.0 * @since 5.4.0 Added the `$unfiltered` parameter. * * @param int $attachment_id Attachment ID. * @param bool $unfiltered Optional. Passed through to `get_attached_file()`. Default false. * @return string|false Path to the original image file or false if the attachment is not an image. */ function wp_get_original_image_path( $attachment_id, $unfiltered = false ) { if ( ! wp_attachment_is_image( $attachment_id ) ) { return false; } $image_meta = wp_get_attachment_metadata( $attachment_id ); $image_file = get_attached_file( $attachment_id, $unfiltered ); if ( empty( $image_meta['original_image'] ) ) { $original_image = $image_file; } else { $original_image = path_join( dirname( $image_file ), $image_meta['original_image'] ); } /** * Filters the path to the original image. * * @since 5.3.0 * * @param string $original_image Path to original image file. * @param int $attachment_id Attachment ID. */ return apply_filters( 'wp_get_original_image_path', $original_image, $attachment_id ); } /** * Retrieves the URL to an original attachment image. * * Similar to `wp_get_attachment_url()` however some images may have been * processed after uploading. In this case this function returns the URL * to the originally uploaded image file. * * @since 5.3.0 * * @param int $attachment_id Attachment post ID. * @return string|false Attachment image URL, false on error or if the attachment is not an image. */ function wp_get_original_image_url( $attachment_id ) { if ( ! wp_attachment_is_image( $attachment_id ) ) { return false; } $image_url = wp_get_attachment_url( $attachment_id ); if ( ! $image_url ) { return false; } $image_meta = wp_get_attachment_metadata( $attachment_id ); if ( empty( $image_meta['original_image'] ) ) { $original_image_url = $image_url; } else { $original_image_url = path_join( dirname( $image_url ), $image_meta['original_image'] ); } /** * Filters the URL to the original attachment image. * * @since 5.3.0 * * @param string $original_image_url URL to original image. * @param int $attachment_id Attachment ID. */ return apply_filters( 'wp_get_original_image_url', $original_image_url, $attachment_id ); } /** * Filters callback which sets the status of an untrashed post to its previous status. * * This can be used as a callback on the `wp_untrash_post_status` filter. * * @since 5.6.0 * * @param string $new_status The new status of the post being restored. * @param int $post_id The ID of the post being restored. * @param string $previous_status The status of the post at the point where it was trashed. * @return string The new status of the post. */ function wp_untrash_post_set_previous_status( $new_status, $post_id, $previous_status ) { return $previous_status; } /** * Returns whether the post can be edited in the block editor. * * @since 5.0.0 * @since 6.1.0 Moved to wp-includes from wp-admin. * * @param int|WP_Post $post Post ID or WP_Post object. * @return bool Whether the post can be edited in the block editor. */ function use_block_editor_for_post( $post ) { $post = get_post( $post ); if ( ! $post ) { return false; } // We're in the meta box loader, so don't use the block editor. if ( is_admin() && isset( $_GET['meta-box-loader'] ) ) { check_admin_referer( 'meta-box-loader', 'meta-box-loader-nonce' ); return false; } $use_block_editor = use_block_editor_for_post_type( $post->post_type ); /** * Filters whether a post is able to be edited in the block editor. * * @since 5.0.0 * * @param bool $use_block_editor Whether the post can be edited or not. * @param WP_Post $post The post being checked. */ return apply_filters( 'use_block_editor_for_post', $use_block_editor, $post ); } /** * Returns whether a post type is compatible with the block editor. * * The block editor depends on the REST API, and if the post type is not shown in the * REST API, then it won't work with the block editor. * * @since 5.0.0 * @since 6.1.0 Moved to wp-includes from wp-admin. * * @param string $post_type The post type. * @return bool Whether the post type can be edited with the block editor. */ function use_block_editor_for_post_type( $post_type ) { if ( ! post_type_exists( $post_type ) ) { return false; } if ( ! post_type_supports( $post_type, 'editor' ) ) { return false; } $post_type_object = get_post_type_object( $post_type ); if ( $post_type_object && ! $post_type_object->show_in_rest ) { return false; } /** * Filters whether a post is able to be edited in the block editor. * * @since 5.0.0 * * @param bool $use_block_editor Whether the post type can be edited or not. Default true. * @param string $post_type The post type being checked. */ return apply_filters( 'use_block_editor_for_post_type', true, $post_type ); } /** * Registers any additional post meta fields. * * @since 6.3.0 Adds `wp_pattern_sync_status` meta field to the wp_block post type so an unsynced option can be added. * * @link https://github.com/WordPress/gutenberg/pull/51144 */ function wp_create_initial_post_meta() { register_post_meta( 'wp_block', 'wp_pattern_sync_status', array( 'sanitize_callback' => 'sanitize_text_field', 'single' => true, 'type' => 'string', 'show_in_rest' => array( 'schema' => array( 'type' => 'string', 'enum' => array( 'partial', 'unsynced' ), ), ), ) ); }