Index: src/wp-includes/comment.php
===================================================================
--- src/wp-includes/comment.php	(revision 43599)
+++ src/wp-includes/comment.php	(working copy)
@@ -442,11 +442,7 @@
  * @return int|bool Meta ID on success, false on failure.
  */
 function add_comment_meta( $comment_id, $meta_key, $meta_value, $unique = false ) {
-	$added = add_metadata( 'comment', $comment_id, $meta_key, $meta_value, $unique );
-	if ( $added ) {
-		wp_cache_set( 'last_changed', microtime(), 'comment' );
-	}
-	return $added;
+	return add_metadata( 'comment', $comment_id, $meta_key, $meta_value, $unique );
 }
 
 /**
@@ -465,11 +461,7 @@
  * @return bool True on success, false on failure.
  */
 function delete_comment_meta( $comment_id, $meta_key, $meta_value = '' ) {
-	$deleted = delete_metadata( 'comment', $comment_id, $meta_key, $meta_value );
-	if ( $deleted ) {
-		wp_cache_set( 'last_changed', microtime(), 'comment' );
-	}
-	return $deleted;
+	return delete_metadata( 'comment', $comment_id, $meta_key, $meta_value );
 }
 
 /**
@@ -506,11 +498,7 @@
  * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
  */
 function update_comment_meta( $comment_id, $meta_key, $meta_value, $prev_value = '' ) {
-	$updated = update_metadata( 'comment', $comment_id, $meta_key, $meta_value, $prev_value );
-	if ( $updated ) {
-		wp_cache_set( 'last_changed', microtime(), 'comment' );
-	}
-	return $updated;
+	return update_metadata( 'comment', $comment_id, $meta_key, $meta_value, $prev_value );
 }
 
 /**
@@ -3512,3 +3500,12 @@
 		'done'           => $done,
 	);
 }
+
+/**
+ * Sets the last changed time for the 'comment' cache group.
+ *
+ * @since 5.0.0
+ */
+function wp_cache_set_comments_last_changed() {
+	wp_cache_set( 'last_changed', microtime(), 'comment' );
+}
Index: src/wp-includes/default-filters.php
===================================================================
--- src/wp-includes/default-filters.php	(revision 43599)
+++ src/wp-includes/default-filters.php	(working copy)
@@ -98,6 +98,29 @@
 // Meta
 add_filter( 'register_meta_args', '_wp_register_meta_args_whitelist', 10, 2 );
 
+// Post meta
+add_action( 'added_post_meta', 'wp_cache_set_posts_last_changed' );
+add_action( 'updated_post_meta', 'wp_cache_set_posts_last_changed' );
+add_action( 'deleted_post_meta', 'wp_cache_set_posts_last_changed' );
+
+// Term meta
+add_action( 'added_term_meta', 'wp_cache_set_terms_last_changed' );
+add_action( 'updated_term_meta', 'wp_cache_set_terms_last_changed' );
+add_action( 'deleted_term_meta', 'wp_cache_set_terms_last_changed' );
+add_filter( 'get_term_metadata', 'wp_check_term_meta_support_prefilter' );
+add_filter( 'add_term_metadata', 'wp_check_term_meta_support_prefilter' );
+add_filter( 'update_term_metadata', 'wp_check_term_meta_support_prefilter' );
+add_filter( 'delete_term_metadata', 'wp_check_term_meta_support_prefilter' );
+add_filter( 'get_term_metadata_by_mid', 'wp_check_term_meta_support_prefilter' );
+add_filter( 'update_term_metadata_by_mid', 'wp_check_term_meta_support_prefilter' );
+add_filter( 'delete_term_metadata_by_mid', 'wp_check_term_meta_support_prefilter' );
+add_filter( 'update_term_metadata_cache', 'wp_check_term_meta_support_prefilter' );
+
+// Comment meta
+add_action( 'added_comment_meta', 'wp_cache_set_comments_last_changed' );
+add_action( 'updated_comment_meta', 'wp_cache_set_comments_last_changed' );
+add_action( 'deleted_comment_meta', 'wp_cache_set_comments_last_changed' );
+
 // Places to balance tags on input
 foreach ( array( 'content_save_pre', 'excerpt_save_pre', 'comment_save_pre', 'pre_comment_content' ) as $filter ) {
 	add_filter( $filter, 'convert_invalid_entities' );
Index: src/wp-includes/meta.php
===================================================================
--- src/wp-includes/meta.php	(revision 43599)
+++ src/wp-includes/meta.php	(working copy)
@@ -613,6 +613,23 @@
 
 	$id_column = ( 'user' == $meta_type ) ? 'umeta_id' : 'meta_id';
 
+	/**
+	 * Filters whether to retrieve metadata of a specific type by meta ID.
+	 *
+	 * The dynamic portion of the hook, `$meta_type`, refers to the meta
+	 * object type (comment, post, term, or user). Returning a non-null value
+	 * will effectively short-circuit the function.
+	 *
+	 * @since 5.0.0
+	 *
+	 * @param mixed $value    The value get_metadata_by_mid() should return.
+	 * @param int   $meta_id  Meta ID.
+	 */
+	$check = apply_filters( "get_{$meta_type}_metadata_by_mid", null, $meta_id );
+	if ( null !== $check ) {
+		return $check;
+	}
+
 	$meta = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table WHERE $id_column = %d", $meta_id ) );
 
 	if ( empty( $meta ) ) {
@@ -660,6 +677,25 @@
 	$column    = sanitize_key( $meta_type . '_id' );
 	$id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id';
 
+	/**
+	 * Filters whether to update metadata of a specific type by meta ID.
+	 *
+	 * The dynamic portion of the hook, `$meta_type`, refers to the meta
+	 * object type (comment, post, term, or user). Returning a non-null value
+	 * will effectively short-circuit the function.
+	 *
+	 * @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|bool $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.
 	if ( $meta = get_metadata_by_mid( $meta_type, $meta_id ) ) {
 		$original_key = $meta->meta_key;
@@ -755,6 +791,23 @@
 	$column    = sanitize_key( $meta_type . '_id' );
 	$id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id';
 
+	/**
+	 * Filters whether to delete metadata of a specific type by meta ID.
+	 *
+	 * The dynamic portion of the hook, `$meta_type`, refers to the meta
+	 * object type (comment, post, term, or user). Returning a non-null value
+	 * will effectively short-circuit the function.
+	 *
+	 * @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.
 	if ( $meta = get_metadata_by_mid( $meta_type, $meta_id ) ) {
 		$object_id = $meta->{$column};
@@ -841,6 +894,23 @@
 
 	$object_ids = array_map( 'intval', $object_ids );
 
+	/**
+	 * Filters whether to update the metadata cache of a specific type.
+	 *
+	 * The dynamic portion of the hook, `$meta_type`, refers to the meta
+	 * object type (comment, post, term, or user). Returning a non-null value
+	 * will effectively short-circuit the function.
+	 *
+	 * @since 5.0.0
+	 *
+	 * @param mixed $check      Whether to allow updating the meta cache of the given type.
+	 * @param array $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';
 	$ids       = array();
 	$cache     = array();
Index: src/wp-includes/ms-blogs.php
===================================================================
--- src/wp-includes/ms-blogs.php	(revision 43599)
+++ src/wp-includes/ms-blogs.php	(working copy)
@@ -662,10 +662,6 @@
  * @return array|false Returns false if there is nothing to update. Returns an array of metadata on success.
  */
 function update_sitemeta_cache( $site_ids ) {
-	if ( ! is_site_meta_supported() ) {
-		return false;
-	}
-
 	return update_meta_cache( 'blog', $site_ids );
 }
 
@@ -1063,21 +1059,7 @@
  * @return int|false Meta ID on success, false on failure.
  */
 function add_site_meta( $site_id, $meta_key, $meta_value, $unique = false ) {
-	// Bail if site meta table is not installed.
-	if ( ! is_site_meta_supported() ) {
-		/* translators: %s: database table name */
-		_doing_it_wrong( __FUNCTION__, sprintf( __( 'The %s table is not installed. Please run the network database upgrade.' ), $GLOBALS['wpdb']->blogmeta ), '5.0.0' );
-		return false;
-	}
-
-	$added = add_metadata( 'blog', $site_id, $meta_key, $meta_value, $unique );
-
-	// Bust site query cache.
-	if ( $added ) {
-		wp_cache_set( 'last_changed', microtime(), 'sites' );
-	}
-
-	return $added;
+	return add_metadata( 'blog', $site_id, $meta_key, $meta_value, $unique );
 }
 
 /**
@@ -1096,21 +1078,7 @@
  * @return bool True on success, false on failure.
  */
 function delete_site_meta( $site_id, $meta_key, $meta_value = '' ) {
-	// Bail if site meta table is not installed.
-	if ( ! is_site_meta_supported() ) {
-		/* translators: %s: database table name */
-		_doing_it_wrong( __FUNCTION__, sprintf( __( 'The %s table is not installed. Please run the network database upgrade.' ), $GLOBALS['wpdb']->blogmeta ), '5.0.0' );
-		return false;
-	}
-
-	$deleted = delete_metadata( 'blog', $site_id, $meta_key, $meta_value );
-
-	// Bust site query cache.
-	if ( $deleted ) {
-		wp_cache_set( 'last_changed', microtime(), 'sites' );
-	}
-
-	return $deleted;
+	return delete_metadata( 'blog', $site_id, $meta_key, $meta_value );
 }
 
 /**
@@ -1126,13 +1094,6 @@
  *               field if $single is true.
  */
 function get_site_meta( $site_id, $key = '', $single = false ) {
-	// Bail if site meta table is not installed.
-	if ( ! is_site_meta_supported() ) {
-		/* translators: %s: database table name */
-		_doing_it_wrong( __FUNCTION__, sprintf( __( 'The %s table is not installed. Please run the network database upgrade.' ), $GLOBALS['wpdb']->blogmeta ), '5.0.0' );
-		return false;
-	}
-
 	return get_metadata( 'blog', $site_id, $key, $single );
 }
 
@@ -1155,21 +1116,7 @@
  *                  false on failure.
  */
 function update_site_meta( $site_id, $meta_key, $meta_value, $prev_value = '' ) {
-	// Bail if site meta table is not installed.
-	if ( ! is_site_meta_supported() ) {
-		/* translators: %s: database table name */
-		_doing_it_wrong( __FUNCTION__, sprintf( __( 'The %s table is not installed. Please run the network database upgrade.' ), $GLOBALS['wpdb']->blogmeta ), '5.0.0' );
-		return false;
-	}
-
-	$updated = update_metadata( 'blog', $site_id, $meta_key, $meta_value, $prev_value );
-
-	// Bust site query cache.
-	if ( $updated ) {
-		wp_cache_set( 'last_changed', microtime(), 'sites' );
-	}
-
-	return $updated;
+	return update_metadata( 'blog', $site_id, $meta_key, $meta_value, $prev_value );
 }
 
 /**
@@ -1181,21 +1128,7 @@
  * @return bool Whether the site meta key was deleted from the database.
  */
 function delete_site_meta_by_key( $meta_key ) {
-	// Bail if site meta table is not installed.
-	if ( ! is_site_meta_supported() ) {
-		/* translators: %s: database table name */
-		_doing_it_wrong( __FUNCTION__, sprintf( __( 'The %s table is not installed. Please run the network database upgrade.' ), $GLOBALS['wpdb']->blogmeta ), '5.0.0' );
-		return false;
-	}
-
-	$deleted = delete_metadata( 'blog', null, $meta_key, '', true );
-
-	// Bust site query cache.
-	if ( $deleted ) {
-		wp_cache_set( 'last_changed', microtime(), 'sites' );
-	}
-
-	return $deleted;
+	return delete_metadata( 'blog', null, $meta_key, '', true );
 }
 
 /**
@@ -1891,3 +1824,30 @@
 function wp_update_blog_public_option_on_site_update( $site_id, $public ) {
 	update_blog_option( $site_id, 'blog_public', $public );
 }
+
+/**
+ * Sets the last changed time for the 'sites' cache group.
+ *
+ * @since 5.0.0
+ */
+function wp_cache_set_sites_last_changed() {
+	wp_cache_set( 'last_changed', microtime(), 'sites' );
+}
+
+/**
+ * Aborts calls to site meta if it is not supported.
+ *
+ * @since 5.0.0
+ *
+ * @param mixed $check Skip-value for whether to proceed site meta function execution.
+ * @return mixed Original value of $check, or false if site meta is not supported.
+ */
+function wp_check_site_meta_support_prefilter( $check ) {
+	if ( ! is_site_meta_supported() ) {
+		/* translators: %s: database table name */
+		_doing_it_wrong( __FUNCTION__, sprintf( __( 'The %s table is not installed. Please run the network database upgrade.' ), $GLOBALS['wpdb']->blogmeta ), '5.0.0' );
+		return false;
+	}
+
+	return $check;
+}
Index: src/wp-includes/ms-default-filters.php
===================================================================
--- src/wp-includes/ms-default-filters.php	(revision 43599)
+++ src/wp-includes/ms-default-filters.php	(working copy)
@@ -51,6 +51,19 @@
 add_action( 'wp_update_site', 'wp_maybe_clean_new_site_cache_on_update', 10, 2 );
 add_action( 'update_blog_public', 'wp_update_blog_public_option_on_site_update', 1, 2 );
 
+// Site meta
+add_action( 'added_blog_meta', 'wp_cache_set_sites_last_changed' );
+add_action( 'updated_blog_meta', 'wp_cache_set_sites_last_changed' );
+add_action( 'deleted_blog_meta', 'wp_cache_set_sites_last_changed' );
+add_filter( 'get_blog_metadata', 'wp_check_site_meta_support_prefilter' );
+add_filter( 'add_blog_metadata', 'wp_check_site_meta_support_prefilter' );
+add_filter( 'update_blog_metadata', 'wp_check_site_meta_support_prefilter' );
+add_filter( 'delete_blog_metadata', 'wp_check_site_meta_support_prefilter' );
+add_filter( 'get_blog_metadata_by_mid', 'wp_check_site_meta_support_prefilter' );
+add_filter( 'update_blog_metadata_by_mid', 'wp_check_site_meta_support_prefilter' );
+add_filter( 'delete_blog_metadata_by_mid', 'wp_check_site_meta_support_prefilter' );
+add_filter( 'update_blog_metadata_cache', 'wp_check_site_meta_support_prefilter' );
+
 // Register Nonce
 add_action( 'signup_hidden_fields', 'signup_nonce_fields' );
 
Index: src/wp-includes/post.php
===================================================================
--- src/wp-includes/post.php	(revision 43599)
+++ src/wp-includes/post.php	(working copy)
@@ -1910,11 +1910,7 @@
 		$post_id = $the_post;
 	}
 
-	$added = add_metadata( 'post', $post_id, $meta_key, $meta_value, $unique );
-	if ( $added ) {
-		wp_cache_set( 'last_changed', microtime(), 'posts' );
-	}
-	return $added;
+	return add_metadata( 'post', $post_id, $meta_key, $meta_value, $unique );
 }
 
 /**
@@ -1938,11 +1934,7 @@
 		$post_id = $the_post;
 	}
 
-	$deleted = delete_metadata( 'post', $post_id, $meta_key, $meta_value );
-	if ( $deleted ) {
-		wp_cache_set( 'last_changed', microtime(), 'posts' );
-	}
-	return $deleted;
+	return delete_metadata( 'post', $post_id, $meta_key, $meta_value );
 }
 
 /**
@@ -1985,11 +1977,7 @@
 		$post_id = $the_post;
 	}
 
-	$updated = update_metadata( 'post', $post_id, $meta_key, $meta_value, $prev_value );
-	if ( $updated ) {
-		wp_cache_set( 'last_changed', microtime(), 'posts' );
-	}
-	return $updated;
+	return update_metadata( 'post', $post_id, $meta_key, $meta_value, $prev_value );
 }
 
 /**
@@ -2001,11 +1989,7 @@
  * @return bool Whether the post meta key was deleted from the database.
  */
 function delete_post_meta_by_key( $post_meta_key ) {
-	$deleted = delete_metadata( 'post', null, $post_meta_key, '', true );
-	if ( $deleted ) {
-		wp_cache_set( 'last_changed', microtime(), 'posts' );
-	}
-	return $deleted;
+	return delete_metadata( 'post', null, $post_meta_key, '', true );
 }
 
 /**
@@ -6785,3 +6769,12 @@
 
 	return $clauses;
 }
+
+/**
+ * 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', microtime(), 'posts' );
+}
Index: src/wp-includes/taxonomy.php
===================================================================
--- src/wp-includes/taxonomy.php	(revision 43599)
+++ src/wp-includes/taxonomy.php	(working copy)
@@ -1211,23 +1211,11 @@
  *                           False on failure.
  */
 function add_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) {
-	// Bail if term meta table is not installed.
-	if ( get_option( 'db_version' ) < 34370 ) {
-		return false;
-	}
-
 	if ( wp_term_is_shared( $term_id ) ) {
 		return new WP_Error( 'ambiguous_term_id', __( 'Term meta cannot be added to terms that are shared between taxonomies.' ), $term_id );
 	}
 
-	$added = add_metadata( 'term', $term_id, $meta_key, $meta_value, $unique );
-
-	// Bust term query cache.
-	if ( $added ) {
-		wp_cache_set( 'last_changed', microtime(), 'terms' );
-	}
-
-	return $added;
+	return add_metadata( 'term', $term_id, $meta_key, $meta_value, $unique );
 }
 
 /**
@@ -1241,19 +1229,7 @@
  * @return bool True on success, false on failure.
  */
 function delete_term_meta( $term_id, $meta_key, $meta_value = '' ) {
-	// Bail if term meta table is not installed.
-	if ( get_option( 'db_version' ) < 34370 ) {
-		return false;
-	}
-
-	$deleted = delete_metadata( 'term', $term_id, $meta_key, $meta_value );
-
-	// Bust term query cache.
-	if ( $deleted ) {
-		wp_cache_set( 'last_changed', microtime(), 'terms' );
-	}
-
-	return $deleted;
+	return delete_metadata( 'term', $term_id, $meta_key, $meta_value );
 }
 
 /**
@@ -1268,11 +1244,6 @@
  * @return mixed If `$single` is false, an array of metadata values. If `$single` is true, a single metadata value.
  */
 function get_term_meta( $term_id, $key = '', $single = false ) {
-	// Bail if term meta table is not installed.
-	if ( get_option( 'db_version' ) < 34370 ) {
-		return false;
-	}
-
 	return get_metadata( 'term', $term_id, $key, $single );
 }
 
@@ -1293,23 +1264,11 @@
  *                           WP_Error when term_id is ambiguous between taxonomies. False on failure.
  */
 function update_term_meta( $term_id, $meta_key, $meta_value, $prev_value = '' ) {
-	// Bail if term meta table is not installed.
-	if ( get_option( 'db_version' ) < 34370 ) {
-		return false;
-	}
-
 	if ( wp_term_is_shared( $term_id ) ) {
 		return new WP_Error( 'ambiguous_term_id', __( 'Term meta cannot be added to terms that are shared between taxonomies.' ), $term_id );
 	}
 
-	$updated = update_metadata( 'term', $term_id, $meta_key, $meta_value, $prev_value );
-
-	// Bust term query cache.
-	if ( $updated ) {
-		wp_cache_set( 'last_changed', microtime(), 'terms' );
-	}
-
-	return $updated;
+	return update_metadata( 'term', $term_id, $meta_key, $meta_value, $prev_value );
 }
 
 /**
@@ -1324,11 +1283,6 @@
  * @return array|false Returns false if there is nothing to update. Returns an array of metadata on success.
  */
 function update_termmeta_cache( $term_ids ) {
-	// Bail if term meta table is not installed.
-	if ( get_option( 'db_version' ) < 34370 ) {
-		return;
-	}
-
 	return update_meta_cache( 'term', $term_ids );
 }
 
@@ -1343,9 +1297,9 @@
  * @return array|false Array with meta data, or false when the meta table is not installed.
  */
 function has_term_meta( $term_id ) {
-	// Bail if term meta table is not installed.
-	if ( get_option( 'db_version' ) < 34370 ) {
-		return false;
+	$check = wp_check_term_meta_support_prefilter( null );
+	if ( null !== $check ) {
+		return $check;
 	}
 
 	global $wpdb;
@@ -4632,3 +4586,28 @@
 
 	return $taxonomy->publicly_queryable;
 }
+
+/**
+ * Sets the last changed time for the 'terms' cache group.
+ *
+ * @since 5.0.0
+ */
+function wp_cache_set_terms_last_changed() {
+	wp_cache_set( 'last_changed', microtime(), 'terms' );
+}
+
+/**
+ * Aborts calls to term meta if it is not supported.
+ *
+ * @since 5.0.0
+ *
+ * @param mixed $check Skip-value for whether to proceed term meta function execution.
+ * @return mixed Original value of $check, or false if term meta is not supported.
+ */
+function wp_check_term_meta_support_prefilter( $check ) {
+	if ( get_option( 'db_version' ) < 34370 ) {
+		return false;
+	}
+
+	return $check;
+}
