diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 3940519d4dc15..80be22202d522 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2262,6 +2262,11 @@ function _add_post_type_submenus() { * A third, optional parameter can also be passed along with a feature to provide * additional information about supporting that feature. * + * When calling this function multiple times for the same post type and feature + * with associative array arguments, the arguments will be merged rather than + * overwritten. This allows multiple calls to add different sub-properties to + * the same feature. + * * Example usage: * * add_post_type_support( 'my_post_type', 'comments' ); @@ -2275,6 +2280,8 @@ function _add_post_type_submenus() { * @since 3.0.0 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter * by adding it to the function signature. + * @since 6.9.0 Multiple calls to add support for the same feature with associative + * array arguments now merge the arguments instead of overwriting them. * * @global array $_wp_post_type_features * @@ -2289,7 +2296,21 @@ function add_post_type_support( $post_type, $feature, ...$args ) { $features = (array) $feature; foreach ( $features as $feature ) { if ( $args ) { - $_wp_post_type_features[ $post_type ][ $feature ] = $args; + // Check if feature already exists with args and if both are associative arrays that should be merged. + if ( + isset( $_wp_post_type_features[ $post_type ][ $feature ][0] ) && + is_array( $_wp_post_type_features[ $post_type ][ $feature ][0] ) && + isset( $args[0] ) && + is_array( $args[0] ) + ) { + // Merge the associative arrays to preserve existing properties. + $_wp_post_type_features[ $post_type ][ $feature ][0] = array_merge( + $_wp_post_type_features[ $post_type ][ $feature ][0], + $args[0] + ); + } else { + $_wp_post_type_features[ $post_type ][ $feature ] = $args; + } } else { $_wp_post_type_features[ $post_type ][ $feature ] = true; } diff --git a/tests/phpunit/tests/post/types.php b/tests/phpunit/tests/post/types.php index 96519586d6cff..cd5092cab5e7e 100644 --- a/tests/phpunit/tests/post/types.php +++ b/tests/phpunit/tests/post/types.php @@ -215,6 +215,231 @@ public function test_post_type_supports() { $this->assertFalse( post_type_supports( 'notaposttype', 'notafeature' ) ); } + /** + * Tests that add_post_type_support() merges array arguments on subsequent calls. + * + * @ticket 64156 + */ + public function test_add_post_type_support_merges_array_arguments() { + register_post_type( 'foo' ); + + // First call with array arguments. + add_post_type_support( + 'foo', + 'editor', + array( + 'default-mode' => 'template-locked', + ) + ); + + $support = get_all_post_type_supports( 'foo' ); + $this->assertIsArray( $support['editor'] ); + $this->assertArrayHasKey( 0, $support['editor'] ); + $this->assertIsArray( $support['editor'][0] ); + $this->assertSame( 'template-locked', $support['editor'][0]['default-mode'] ); + + // Second call with different array arguments should merge. + add_post_type_support( + 'foo', + 'editor', + array( + 'block-comments' => true, + ) + ); + + $support = get_all_post_type_supports( 'foo' ); + $this->assertIsArray( $support['editor'] ); + $this->assertArrayHasKey( 0, $support['editor'] ); + $this->assertIsArray( $support['editor'][0] ); + $this->assertSame( 'template-locked', $support['editor'][0]['default-mode'] ); + $this->assertTrue( $support['editor'][0]['block-comments'] ); + + // Third call with yet another property should merge with both previous. + add_post_type_support( + 'foo', + 'editor', + array( + 'another-option' => 'test-value', + ) + ); + + $support = get_all_post_type_supports( 'foo' ); + $this->assertSame( 'template-locked', $support['editor'][0]['default-mode'] ); + $this->assertTrue( $support['editor'][0]['block-comments'] ); + $this->assertSame( 'test-value', $support['editor'][0]['another-option'] ); + + _unregister_post_type( 'foo' ); + } + + /** + * Tests that add_post_type_support() overwrites values when called with the same key. + * + * @ticket 64156 + */ + public function test_add_post_type_support_overwrites_same_key() { + register_post_type( 'foo' ); + + add_post_type_support( + 'foo', + 'editor', + array( + 'default-mode' => 'template-locked', + ) + ); + + $support = get_all_post_type_supports( 'foo' ); + $this->assertSame( 'template-locked', $support['editor'][0]['default-mode'] ); + + // Calling with same key but different value should overwrite. + add_post_type_support( + 'foo', + 'editor', + array( + 'default-mode' => 'unlocked', + ) + ); + + $support = get_all_post_type_supports( 'foo' ); + $this->assertSame( 'unlocked', $support['editor'][0]['default-mode'] ); + + _unregister_post_type( 'foo' ); + } + + /** + * Tests backwards compatibility: calling add_post_type_support() without args still works. + * + * @ticket 64156 + */ + public function test_add_post_type_support_without_args() { + register_post_type( 'foo' ); + + add_post_type_support( 'foo', 'custom-fields' ); + + $this->assertTrue( post_type_supports( 'foo', 'custom-fields' ) ); + $support = get_all_post_type_supports( 'foo' ); + $this->assertTrue( $support['custom-fields'] ); + + _unregister_post_type( 'foo' ); + } + + /** + * Tests backwards compatibility: calling add_post_type_support() with args after + * setting it to true should overwrite with args. + * + * @ticket 64156 + */ + public function test_add_post_type_support_with_args_after_true() { + register_post_type( 'foo' ); + + // First call without args sets to true. + add_post_type_support( 'foo', 'editor' ); + + $support = get_all_post_type_supports( 'foo' ); + $this->assertTrue( $support['editor'] ); + + // Second call with args should overwrite true with args. + add_post_type_support( + 'foo', + 'editor', + array( + 'default-mode' => 'template-locked', + ) + ); + + $support = get_all_post_type_supports( 'foo' ); + $this->assertIsArray( $support['editor'] ); + $this->assertSame( 'template-locked', $support['editor'][0]['default-mode'] ); + + _unregister_post_type( 'foo' ); + } + + /** + * Tests backwards compatibility: calling add_post_type_support() without args after + * setting it with args should overwrite with true. + * + * @ticket 64156 + */ + public function test_add_post_type_support_without_args_after_array() { + register_post_type( 'foo' ); + + // First call with args. + add_post_type_support( + 'foo', + 'editor', + array( + 'default-mode' => 'template-locked', + ) + ); + + $support = get_all_post_type_supports( 'foo' ); + $this->assertIsArray( $support['editor'] ); + $this->assertSame( 'template-locked', $support['editor'][0]['default-mode'] ); + + // Second call without args should overwrite args with true. + add_post_type_support( 'foo', 'editor' ); + + $support = get_all_post_type_supports( 'foo' ); + $this->assertTrue( $support['editor'] ); + + _unregister_post_type( 'foo' ); + } + + /** + * Tests that add_post_type_support() can add multiple features at once. + * + * @ticket 64156 + */ + public function test_add_post_type_support_multiple_features() { + register_post_type( 'foo' ); + + add_post_type_support( 'foo', array( 'title', 'editor', 'thumbnail' ) ); + + $this->assertTrue( post_type_supports( 'foo', 'title' ) ); + $this->assertTrue( post_type_supports( 'foo', 'editor' ) ); + $this->assertTrue( post_type_supports( 'foo', 'thumbnail' ) ); + + _unregister_post_type( 'foo' ); + } + + /** + * Tests that add_post_type_support() works with nested array arguments. + * + * @ticket 64156 + */ + public function test_add_post_type_support_with_nested_arrays() { + register_post_type( 'foo' ); + + add_post_type_support( + 'foo', + 'editor', + array( + 'allowed_blocks' => array( 'core/paragraph', 'core/heading' ), + ) + ); + + $support = get_all_post_type_supports( 'foo' ); + $this->assertIsArray( $support['editor'][0]['allowed_blocks'] ); + $this->assertContains( 'core/paragraph', $support['editor'][0]['allowed_blocks'] ); + $this->assertContains( 'core/heading', $support['editor'][0]['allowed_blocks'] ); + + // Adding another nested array should merge. + add_post_type_support( + 'foo', + 'editor', + array( + 'disallowed_blocks' => array( 'core/code' ), + ) + ); + + $support = get_all_post_type_supports( 'foo' ); + $this->assertIsArray( $support['editor'][0]['allowed_blocks'] ); + $this->assertIsArray( $support['editor'][0]['disallowed_blocks'] ); + $this->assertContains( 'core/paragraph', $support['editor'][0]['allowed_blocks'] ); + $this->assertContains( 'core/code', $support['editor'][0]['disallowed_blocks'] ); + + _unregister_post_type( 'foo' ); + } + /** * @ticket 21586 * @ticket 41172