Index: src/wp-includes/comment.php
===================================================================
--- src/wp-includes/comment.php	(revision 43728)
+++ src/wp-includes/comment.php	(working copy)
@@ -422,12 +422,8 @@
  * @param bool $unique Optional, default is false. Whether the same key should not be added.
  * @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;
+function add_comment_meta( $comment_id, $meta_key, $meta_value, $unique = false ) {
+	return add_metadata( 'comment', $comment_id, $meta_key, $meta_value, $unique );
 }
 
 /**
@@ -445,12 +441,8 @@
  * @param mixed $meta_value Optional. Metadata value.
  * @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;
+function delete_comment_meta( $comment_id, $meta_key, $meta_value = '' ) {
+	return delete_metadata( 'comment', $comment_id, $meta_key, $meta_value );
 }
 
 /**
@@ -465,8 +457,8 @@
  * @return mixed Will be an array if $single is false. Will be value of meta data field if $single
  *  is true.
  */
-function get_comment_meta($comment_id, $key = '', $single = false) {
-	return get_metadata('comment', $comment_id, $key, $single);
+function get_comment_meta( $comment_id, $key = '', $single = false ) {
+	return get_metadata( 'comment', $comment_id, $key, $single );
 }
 
 /**
@@ -486,12 +478,8 @@
  * @param mixed $prev_value Optional. Previous value to check before removing.
  * @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;
+function update_comment_meta( $comment_id, $meta_key, $meta_value, $prev_value = '' ) {
+	return update_metadata( 'comment', $comment_id, $meta_key, $meta_value, $prev_value );
 }
 
 /**
@@ -3061,7 +3049,7 @@
 		 * @param int $comment_post_ID Post ID.
 		 */
 		do_action( 'comment_on_draft', $comment_post_ID );
-		
+
 		if ( current_user_can( 'read_post', $comment_post_ID ) ) {
 			return new WP_Error( 'comment_on_draft', __( 'Sorry, comments are not allowed for this item.' ), 403 );
 		} else {
@@ -3387,3 +3375,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 43728)
+++ src/wp-includes/default-filters.php	(working copy)
@@ -89,6 +89,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 43728)
+++ src/wp-includes/meta.php	(working copy)
@@ -586,6 +586,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 ) )
@@ -631,6 +648,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;
@@ -725,6 +761,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};
@@ -811,6 +864,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/post.php
===================================================================
--- src/wp-includes/post.php	(revision 43728)
+++ src/wp-includes/post.php	(working copy)
@@ -1787,11 +1787,7 @@
 	if ( $the_post = wp_is_post_revision($post_id) )
 		$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 );
 }
 
 /**
@@ -1814,11 +1810,7 @@
 	if ( $the_post = wp_is_post_revision($post_id) )
 		$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 );
 }
 
 /**
@@ -1860,11 +1852,7 @@
 	if ( $the_post = wp_is_post_revision($post_id) )
 		$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 );
 }
 
 /**
@@ -1876,11 +1864,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 );
 }
 
 /**
@@ -6475,3 +6459,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 43728)
+++ src/wp-includes/taxonomy.php	(working copy)
@@ -1135,23 +1135,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 );
 }
 
 /**
@@ -1165,19 +1153,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 );
 }
 
 /**
@@ -1192,11 +1168,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 );
 }
 
@@ -1217,23 +1188,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 );
 }
 
 /**
@@ -1248,11 +1207,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 );
 }
 
@@ -1267,9 +1221,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;
@@ -4341,3 +4295,28 @@
 
 	return $parent;
 }
+
+/**
+ * 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;
+}
Index: tests/phpunit/tests/comment/metaCache.php
===================================================================
--- tests/phpunit/tests/comment/metaCache.php	(revision 43728)
+++ tests/phpunit/tests/comment/metaCache.php	(working copy)
@@ -210,4 +210,41 @@
 		$num_queries++;
 		$this->assertSame( $num_queries, $wpdb->num_queries );
 	}
+
+	/**
+	 * @ticket 44467
+	 */
+	public function test_add_metadata_sets_comments_last_changed() {
+		$comment_id = self::factory()->comment->create();
+
+		wp_cache_delete( 'last_changed', 'comment' );
+
+		$this->assertInternalType( 'integer', add_metadata( 'comment', $comment_id, 'foo', 'bar' ) );
+		$this->assertNotFalse( wp_cache_get_last_changed( 'comment' ) );
+	}
+
+	/**
+	 * @ticket 44467
+	 */
+	public function test_update_metadata_sets_comments_last_changed() {
+		$comment_id = self::factory()->comment->create();
+
+		wp_cache_delete( 'last_changed', 'comment' );
+
+		$this->assertInternalType( 'integer', update_metadata( 'comment', $comment_id, 'foo', 'bar' ) );
+		$this->assertNotFalse( wp_cache_get_last_changed( 'comment' ) );
+	}
+
+	/**
+	 * @ticket 44467
+	 */
+	public function test_delete_metadata_sets_comments_last_changed() {
+		$comment_id = self::factory()->comment->create();
+
+		update_metadata( 'comment', $comment_id, 'foo', 'bar' );
+		wp_cache_delete( 'last_changed', 'comment' );
+
+		$this->assertTrue( delete_metadata( 'comment', $comment_id, 'foo' ) );
+		$this->assertNotFalse( wp_cache_get_last_changed( 'comment' ) );
+	}
 }
Index: tests/phpunit/tests/post/meta.php
===================================================================
--- tests/phpunit/tests/post/meta.php	(revision 43728)
+++ tests/phpunit/tests/post/meta.php	(working copy)
@@ -305,4 +305,41 @@
 			array( '', 'registered_key3' ),
 		);
   	}
+
+	/**
+	 * @ticket 44467
+	 */
+	public function test_add_metadata_sets_posts_last_changed() {
+		$post_id = self::factory()->post->create();
+
+		wp_cache_delete( 'last_changed', 'posts' );
+
+		$this->assertInternalType( 'integer', add_metadata( 'post', $post_id, 'foo', 'bar' ) );
+		$this->assertNotFalse( wp_cache_get_last_changed( 'posts' ) );
+	}
+
+	/**
+	 * @ticket 44467
+	 */
+	public function test_update_metadata_sets_posts_last_changed() {
+		$post_id = self::factory()->post->create();
+
+		wp_cache_delete( 'last_changed', 'posts' );
+
+		$this->assertInternalType( 'integer', update_metadata( 'post', $post_id, 'foo', 'bar' ) );
+		$this->assertNotFalse( wp_cache_get_last_changed( 'posts' ) );
+	}
+
+	/**
+	 * @ticket 44467
+	 */
+	public function test_delete_metadata_sets_posts_last_changed() {
+		$post_id = self::factory()->post->create();
+
+		update_metadata( 'post', $post_id, 'foo', 'bar' );
+		wp_cache_delete( 'last_changed', 'posts' );
+
+		$this->assertTrue( delete_metadata( 'post', $post_id, 'foo' ) );
+		$this->assertNotFalse( wp_cache_get_last_changed( 'posts' ) );
+	}
 }
Index: tests/phpunit/tests/term/meta.php
===================================================================
--- tests/phpunit/tests/term/meta.php	(revision 43728)
+++ tests/phpunit/tests/term/meta.php	(working copy)
@@ -509,4 +509,62 @@
 			array( '', 'registered_key3' ),
 		);
 	}
+
+	/**
+	 * @ticket 44467
+	 */
+	public function test_add_metadata_sets_terms_last_changed() {
+		$term_id = self::factory()->term->create();
+
+		wp_cache_delete( 'last_changed', 'terms' );
+
+		$this->assertInternalType( 'integer', add_metadata( 'term', $term_id, 'foo', 'bar' ) );
+		$this->assertNotFalse( wp_cache_get_last_changed( 'terms' ) );
+	}
+
+	/**
+	 * @ticket 44467
+	 */
+	public function test_update_metadata_sets_terms_last_changed() {
+		$term_id = self::factory()->term->create();
+
+		wp_cache_delete( 'last_changed', 'terms' );
+
+		$this->assertInternalType( 'integer', update_metadata( 'term', $term_id, 'foo', 'bar' ) );
+		$this->assertNotFalse( wp_cache_get_last_changed( 'terms' ) );
+	}
+
+	/**
+	 * @ticket 44467
+	 */
+	public function test_delete_metadata_sets_terms_last_changed() {
+		$term_id = self::factory()->term->create();
+
+		update_metadata( 'term', $term_id, 'foo', 'bar' );
+		wp_cache_delete( 'last_changed', 'terms' );
+
+		$this->assertTrue( delete_metadata( 'term', $term_id, 'foo' ) );
+		$this->assertNotFalse( wp_cache_get_last_changed( 'terms' ) );
+	}
+
+	/**
+	 * @ticket 44467
+	 */
+	public function test_metadata_functions_respect_term_meta_support() {
+		$term_id = self::factory()->term->create();
+
+		$meta_id = add_metadata( 'term', $term_id, 'foo', 'bar' );
+
+		// Set database version to last version before term meta support.
+		update_option( 'db_version', 34369 );
+
+		$this->assertFalse( get_metadata( 'term', $term_id, 'foo', true ) );
+		$this->assertFalse( add_metadata( 'term', $term_id, 'foo', 'bar' ) );
+		$this->assertFalse( update_metadata( 'term', $term_id, 'foo', 'bar' ) );
+		$this->assertFalse( delete_metadata( 'term', $term_id, 'foo' ) );
+		$this->assertFalse( get_metadata_by_mid( 'term', $meta_id ) );
+		$this->assertFalse( update_metadata_by_mid( 'term', $meta_id, 'baz' ) );
+		$this->assertFalse( delete_metadata_by_mid( 'term', $meta_id ) );
+		$this->assertFalse( update_meta_cache( 'term', array( $term_id ) ) );
+	}
 }
