Index: src/wp-includes/class-wp-post-type.php
===================================================================
--- src/wp-includes/class-wp-post-type.php	(revision 38274)
+++ src/wp-includes/class-wp-post-type.php	(working copy)
@@ -325,7 +325,7 @@
 	public $rewrite;
 
 	/**
-	 * The features supported by the post type.
+	 * The features supported by the post type, per post type definition.
 	 *
 	 * @since 4.6.0
 	 * @access public
@@ -334,6 +334,15 @@
 	public $supports;
 
 	/**
+	 * All features supported by the post type.
+	 *
+	 * @since 4.7.0
+	 * @access private
+	 * @var array $features
+	 */
+	private $features = array();
+
+	/**
 	 * Constructor.
 	 *
 	 * Will populate object properties from the provided arguments and assign other
@@ -342,6 +351,8 @@
 	 * @since 4.6.0
 	 * @access public
 	 *
+	 * @global array $_wp_post_type_features
+	 *
 	 * @see register_post_type()
 	 *
 	 * @param string       $post_type Post type key.
@@ -351,6 +362,11 @@
 	public function __construct( $post_type, $args = array() ) {
 		$this->name = $post_type;
 
+		if ( ! empty( $GLOBALS['_wp_post_type_features'][ $this->name ] ) ) {
+			$this->features = $GLOBALS['_wp_post_type_features'][ $this->name ];
+			unset( $GLOBALS['_wp_post_type_features'][ $this->name ] );
+		}
+
 		$this->set_props( $args );
 	}
 
@@ -511,11 +527,11 @@
 	 */
 	public function add_supports() {
 		if ( ! empty( $this->supports ) ) {
-			add_post_type_support( $this->name, $this->supports );
+			$this->add_support( $this->supports );
 			unset( $this->supports );
 		} elseif ( false !== $this->supports ) {
 			// Add default features.
-			add_post_type_support( $this->name, array( 'title', 'editor' ) );
+			$this->add_support( array( 'title', 'editor' ) );
 		}
 	}
 
@@ -602,17 +618,123 @@
 	}
 
 	/**
+	 * Registers support of certain features for the post type.
+	 *
+	 * All core features are directly associated with a functional area of the edit
+	 * screen, such as the editor or a meta box. Features include: 'title', 'editor',
+	 * 'comments', 'revisions', 'trackbacks', 'author', 'excerpt', 'page-attributes',
+	 * 'thumbnail', 'custom-fields', and 'post-formats'.
+	 *
+	 * Additionally, the 'revisions' feature dictates whether the post type will
+	 * store revisions, and the 'comments' feature dictates whether the comments
+	 * count will show on the edit screen.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @param string|array $feature The feature being added, accepts an array of
+	 *                              feature strings or a single string.
+	 * @param bool         $value   Optional. The value to give to the feature. Default
+	 *                              true.
+	 * @param string       $subtype Optional. A subtype to add this feature to. An
+	 *                              empty value will add this feature to the overall
+	 *                              post type. Default empty.
+	 */
+	public function add_support( $feature, $value = true, $subtype = '' ) {
+		$features = (array) $feature;
+		foreach ( $features as $feature ) {
+			$this->features[ $subtype ][ $feature ] = $value;
+		}
+	}
+
+	/**
+	 * Removes support for a feature from the post type.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @param string $feature The feature being removed.
+	 * @param string $subtype Optional. A subtype to remove this feature from. An
+	 *                        empty value will remove this feature from the overall
+	 *                        post type. Default empty.
+	 */
+	public function remove_support( $feature, $subtype = '' ) {
+		unset( $this->features[ $subtype ][ $feature ] );
+	}
+
+	/**
+	 * Gets all the post type features.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @param string $subtype Optional. A subtype to get its features. An
+	 *                        empty value will return the overall post type
+	 *                        features. Default empty.
+	 * @return array Post type supports list.
+	 */
+	public function get_all_supports( $subtype = '' ) {
+		return $this->features[ $subtype ];
+	}
+
+	/**
+	 * Checks the post type's support for a given feature.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @param string $feature The feature being checked.
+	 * @param string $subtype Optional. A subtype to check in its features. An
+	 *                        empty value will check within the overall post type
+	 *                        features. Default empty.
+	 * @return bool Whether the post type supports the given feature.
+	 */
+	public function supports( $feature, $subtype = '' ) {
+		return isset( $this->features[ $subtype ][ $feature ] );
+	}
+
+	/**
+	 * Retrieves a list of post type and subtype names that support a specific feature.
+	 *
+	 * If the features are supported by the overall post type, the post type name is
+	 * included in the list. If the features are supported by a specific subtype, a
+	 * string consisting of post type name and subtype name delimited by a colon is
+	 * included in the list.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @param array|string $feature  Single feature or an array of features the post type should support.
+	 * @param string       $operator Optional. The logical operation to perform. 'or' means
+	 *                               only one element from the array needs to match; 'and'
+	 *                               means all elements must match; 'not' means no elements may
+	 *                               match. Default 'and'.
+	 * @return array A list of post type and subtype names.
+	 */
+	public function get_by_support( $feature, $operator = 'and' ) {
+		$features = array_fill_keys( (array) $feature, true );
+		$subtypes = array_keys( wp_filter_object_list( $this->features, $features, $operator ) );
+
+		$names = array();
+		foreach ( $subtypes as $subtype ) {
+			if ( empty( $subtype ) ) {
+				$names[] = $this->name;
+			} else {
+				$names[] = $this->name . ':' . $subtype;
+			}
+		}
+
+		return $names;
+	}
+
+	/**
 	 * Removes the features support for the post type.
 	 *
 	 * @since 4.6.0
 	 * @access public
-	 *
-	 * @global array $_wp_post_type_features Post type features.
 	 */
 	public function remove_supports() {
-		global $_wp_post_type_features;
-
-		unset( $_wp_post_type_features[ $this->name ] );
+		$this->features = array();
 	}
 
 	/**
Index: src/wp-includes/post.php
===================================================================
--- src/wp-includes/post.php	(revision 38274)
+++ src/wp-includes/post.php	(working copy)
@@ -1398,6 +1398,7 @@
  * count will show on the edit screen.
  *
  * @since 3.0.0
+ * @since 4.7.0 Uses `WP_Post_Type` to handle features.
  *
  * @global array $_wp_post_type_features
  *
@@ -1406,15 +1407,26 @@
  *                                feature strings or a single string.
  */
 function add_post_type_support( $post_type, $feature ) {
-	global $_wp_post_type_features;
+	$subtype = '';
+	if ( strpos( $post_type, ':' ) ) {
+		list( $post_type, $subtype ) = explode( ':', $post_type );
+	}
 
-	$features = (array) $feature;
-	foreach ($features as $feature) {
-		if ( func_num_args() == 2 )
-			$_wp_post_type_features[$post_type][$feature] = true;
-		else
-			$_wp_post_type_features[$post_type][$feature] = array_slice( func_get_args(), 2 );
+	if ( func_num_args() == 2 ) {
+		$value = true;
+	} else {
+		$value = array_slice( func_get_args(), 2 );
 	}
+
+	$ptype_obj = get_post_type_object( $post_type );
+	if ( ! $ptype_obj ) {
+		_doing_it_wrong( __FUNCTION__, sprintf( __( 'The post type %s is not registered. Post type features should only be added to existing post types.' ), $post_type ), '4.7.0' );
+
+		$GLOBALS['_wp_post_type_features'][ $post_type ][ $subtype ][ $feature ] = $value;
+		return;
+	}
+
+	$ptype_obj->add_support( $feature, $value, $subtype );
 }
 
 /**
@@ -1421,6 +1433,7 @@
  * Remove support for a feature from a post type.
  *
  * @since 3.0.0
+ * @since 4.7.0 Uses `WP_Post_Type` to handle features.
  *
  * @global array $_wp_post_type_features
  *
@@ -1428,9 +1441,20 @@
  * @param string $feature   The feature being removed.
  */
 function remove_post_type_support( $post_type, $feature ) {
-	global $_wp_post_type_features;
+	$subtype = '';
+	if ( strpos( $post_type, ':' ) ) {
+		list( $post_type, $subtype ) = explode( ':', $post_type );
+	}
 
-	unset( $_wp_post_type_features[ $post_type ][ $feature ] );
+	$ptype_obj = get_post_type_object( $post_type );
+	if ( ! $ptype_obj ) {
+		_doing_it_wrong( __FUNCTION__, sprintf( __( 'The post type %s is not registered. Post type features should only be removed from existing post types.' ), $post_type ), '4.7.0' );
+
+		unset( $GLOBALS['_wp_post_type_features'][ $post_type ][ $subtype ][ $feature ] );
+		return;
+	}
+
+	$ptype_obj->remove_support( $feature, $subtype );
 }
 
 /**
@@ -1437,6 +1461,7 @@
  * Get all the post type features
  *
  * @since 3.4.0
+ * @since 4.7.0 Uses `WP_Post_Type` to handle features.
  *
  * @global array $_wp_post_type_features
  *
@@ -1444,12 +1469,20 @@
  * @return array Post type supports list.
  */
 function get_all_post_type_supports( $post_type ) {
-	global $_wp_post_type_features;
+	$subtype = '';
+	if ( strpos( $post_type, ':' ) ) {
+		list( $post_type, $subtype ) = explode( ':', $post_type );
+	}
 
-	if ( isset( $_wp_post_type_features[$post_type] ) )
-		return $_wp_post_type_features[$post_type];
+	$ptype_obj = get_post_type_object( $post_type );
+	if ( ! $ptype_obj ) {
+		if ( isset( $GLOBALS['_wp_post_type_features'][ $post_type ][ $subtype ] ) ) {
+			return $GLOBALS['_wp_post_type_features'][ $post_type ][ $subtype ];
+		}
+		return array();
+	}
 
-	return array();
+	return $ptype_obj->get_all_supports( $subtype );
 }
 
 /**
@@ -1456,6 +1489,7 @@
  * Check a post type's support for a given feature.
  *
  * @since 3.0.0
+ * @since 4.7.0 Uses `WP_Post_Type` to handle features.
  *
  * @global array $_wp_post_type_features
  *
@@ -1464,9 +1498,17 @@
  * @return bool Whether the post type supports the given feature.
  */
 function post_type_supports( $post_type, $feature ) {
-	global $_wp_post_type_features;
+	$subtype = '';
+	if ( strpos( $post_type, ':' ) ) {
+		list( $post_type, $subtype ) = explode( ':', $post_type );
+	}
 
-	return ( isset( $_wp_post_type_features[$post_type][$feature] ) );
+	$ptype_obj = get_post_type_object( $post_type );
+	if ( ! $ptype_obj ) {
+		return isset( $GLOBALS['_wp_post_type_features'][ $post_type ][ $subtype ][ $feature ] );
+	}
+
+	return $ptype_obj->supports( $feature, $subtype );
 }
 
 /**
@@ -1473,8 +1515,9 @@
  * Retrieves a list of post type names that support a specific feature.
  *
  * @since 4.5.0
+ * @since 4.7.0 Uses `WP_Post_Type` to handle features.
  *
- * @global array $_wp_post_type_features Post type features
+ * @global array $_wp_post_type_features
  *
  * @param array|string $feature  Single feature or an array of features the post types should support.
  * @param string       $operator Optional. The logical operation to perform. 'or' means
@@ -1484,11 +1527,29 @@
  * @return array A list of post type names.
  */
 function get_post_types_by_support( $feature, $operator = 'and' ) {
-	global $_wp_post_type_features;
+	$names = array();
 
-	$features = array_fill_keys( (array) $feature, true );
+	$ptype_objects = get_post_types( array(), 'objects' );
+	foreach ( $ptype_objects as $ptype_obj ) {
+		$names = array_merge( $names, $ptype_obj->get_by_support( $feature, $operator ) );
+	}
 
-	return array_keys( wp_filter_object_list( $_wp_post_type_features, $features, $operator ) );
+	if ( ! empty( $GLOBALS['_wp_post_type_features'] ) ) {
+		$features = array_fill_keys( (array) $feature, true );
+
+		foreach ( $GLOBALS['_wp_post_type_features'] as $post_type => $subtypes ) {
+			$subtypes = array_keys( wp_filter_object_list( $subtypes, $features, $operator ) );
+			foreach ( $subtypes as $subtype ) {
+				if ( empty( $subtype ) ) {
+					$names[] = $post_type;
+				} else {
+					$names[] = $post_type . ':' . $subtype;
+				}
+			}
+		}
+	}
+
+	return $names;
 }
 
 /**
Index: tests/phpunit/tests/post/typeSupport.php
===================================================================
--- tests/phpunit/tests/post/typeSupport.php	(revision 0)
+++ tests/phpunit/tests/post/typeSupport.php	(working copy)
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * @group post
+ */
+class Tests_Post_Type_Support extends WP_UnitTestCase {
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_add_post_type_support_basic() {
+		$post_type = rand_str( 20 );
+		$feature = 'custom_feature_name';
+
+		register_post_type( $post_type );
+		add_post_type_support( $post_type, $feature );
+		$this->assertTrue( post_type_supports( $post_type, $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 * @expectedIncorrectUsage add_post_type_support
+	 */
+	public function test_add_post_type_support_before_register_post_type() {
+		$post_type = rand_str( 20 );
+		$feature = 'custom_feature_name';
+
+		add_post_type_support( $post_type, $feature );
+		$this->assertTrue( post_type_supports( $post_type, $feature ) );
+
+		register_post_type( $post_type );
+		$this->assertTrue( post_type_supports( $post_type, $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_remove_post_type_support_basic() {
+		$post_type = rand_str( 20 );
+		$feature = 'editor';
+
+		register_post_type( $post_type );
+		remove_post_type_support( $post_type, $feature );
+		$this->assertFalse( post_type_supports( $post_type, $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 * @expectedIncorrectUsage remove_post_type_support
+	 */
+	public function test_remove_post_type_support_before_register_post_type() {
+		$post_type = rand_str( 20 );
+		$feature = 'custom_feature_name';
+		$default_feature = 'editor';
+
+		remove_post_type_support( $post_type, $feature );
+		$this->assertFalse( post_type_supports( $post_type, $feature ) );
+
+		// Features are added on post type registration, so removing them before shouldn't matter.
+		remove_post_type_support( $post_type, $default_feature );
+		register_post_type( $post_type );
+		$this->assertTrue( post_type_supports( $post_type, $default_feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 * @expectedIncorrectUsage add_post_type_support
+	 */
+	public function test_get_post_types_by_support_mixed() {
+		$registered_post_type = rand_str( 20 );
+		$unregistered_post_type = rand_str( 20 );
+		$feature = 'custom_feature_name';
+
+		register_post_type( $registered_post_type );
+
+		add_post_type_support( $registered_post_type, $feature );
+		add_post_type_support( $unregistered_post_type, $feature );
+		$this->assertEqualSets( array( $registered_post_type, $unregistered_post_type ), get_post_types_by_support( $feature ) );
+	}
+}

Property changes on: tests/phpunit/tests/post/typeSupport.php
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: tests/phpunit/tests/post/wpPostType.php
===================================================================
--- tests/phpunit/tests/post/wpPostType.php	(revision 38274)
+++ tests/phpunit/tests/post/wpPostType.php	(working copy)
@@ -148,4 +148,146 @@
 		$this->assertEqualSets( array( 'post_tag' ), $taxonomies );
 		$this->assertEqualSets( array(), $taxonomies_after );
 	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_add_support_basic() {
+		$post_type = rand_str( 20 );
+		$post_type_object = register_post_type( $post_type );
+
+		$feature = 'custom_feature_name';
+
+		$post_type_object->add_support( $feature );
+		$this->assertTrue( post_type_supports( $post_type, $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_add_support_with_subtype() {
+		$post_type = rand_str( 20 );
+		$post_type_object = register_post_type( $post_type );
+
+		$subtype = rand_str();
+		$feature = 'custom_feature_name';
+
+		$post_type_object->add_support( $feature, true, $subtype );
+		$this->assertTrue( post_type_supports( $post_type . ':' . $subtype, $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_remove_support_basic() {
+		$post_type = rand_str( 20 );
+		$post_type_object = register_post_type( $post_type );
+
+		$feature = 'editor';
+
+		$post_type_object->remove_support( $feature );
+		$this->assertFalse( post_type_supports( $post_type, $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_remove_support_with_subtype() {
+		$post_type = rand_str( 20 );
+		$post_type_object = register_post_type( $post_type );
+
+		$subtype = rand_str();
+		$feature = 'custom_feature_name';
+
+		add_post_type_support( $post_type . ':' . $subtype, $feature );
+		$post_type_object->remove_support( $feature, $subtype );
+		$this->assertFalse( post_type_supports( $post_type . ':' . $subtype, $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_get_all_supports_basic() {
+		$post_type = rand_str( 20 );
+		$post_type_object = register_post_type( $post_type );
+
+		$feature = 'custom_feature_name';
+
+		add_post_type_support( $post_type, $feature );
+		$this->assertEqualSets( array(
+			'title' => true,
+			'editor' => true,
+			$feature => true,
+		), $post_type_object->get_all_supports() );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_get_all_supports_with_subtype() {
+		$post_type = rand_str( 20 );
+		$post_type_object = register_post_type( $post_type );
+
+		$subtype = rand_str();
+		$features = array( 'custom_feature_name', 'another_feature_name' );
+
+		add_post_type_support( $post_type . ':' . $subtype, $features );
+		$this->assertEqualSets( array_fill_keys( $features, true ), $post_type_object->get_all_supports( $subtype ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_supports_basic() {
+		$post_type = rand_str( 20 );
+		$post_type_object = register_post_type( $post_type );
+
+		$feature = 'custom_feature_name';
+
+		add_post_type_support( $post_type, $feature );
+		$this->assertTrue( $post_type_object->supports( $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_supports_with_subtype() {
+		$post_type = rand_str( 20 );
+		$post_type_object = register_post_type( $post_type );
+
+		$subtype = rand_str();
+		$feature = 'custom_feature_name';
+
+		add_post_type_support( $post_type . ':' . $subtype, $feature );
+		$this->assertTrue( $post_type_object->supports( $feature, $subtype ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_get_by_support() {
+		$post_type = rand_str( 20 );
+		$post_type_object = register_post_type( $post_type );
+
+		$subtype1 = rand_str();
+		$subtype2 = rand_str();
+		$feature = 'custom_feature_name';
+
+		add_post_type_support( $post_type, $feature );
+		add_post_type_support( $post_type . ':' . $subtype2, $feature );
+		$this->assertEqualSets( array( $post_type, $post_type . ':' . $subtype2 ), $post_type_object->get_by_support( $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_migrate_support_from_global() {
+		$post_type = rand_str();
+
+		$feature = 'custom_feature_name';
+
+		$GLOBALS['_wp_post_type_features'][ $post_type ][''][ $feature ] = true;
+		$post_type_object = new WP_Post_Type( $post_type );
+		$this->assertTrue( $post_type_object->supports( $feature ) );
+	}
 }
