From 1d2f18380fb9e4db0895288e7b02ff43cede561a Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sat, 29 Aug 2026 20:23:43 -0700 Subject: [PATCH 01/11] Rebuild post type meta capabilities when unregistering a post type The global `$post_type_meta_caps` registry is keyed by custom capability name, so a single entry may be owed to any number of registered post types. `WP_Post_Type::remove_rewrite_rules()` removed entries by subtracting every value of the unregistered post type's own `$cap` object, which: * deleted mappings that other, still-registered post types sharing a capability type continue to depend on; * deleted entries for post types registered with `map_meta_cap` set to `false`, which never stored any to begin with; * treated primitive capabilities as meta capabilities, since only the read, delete and edit capabilities are ever stored. Once a mapping is gone, `map_meta_cap()` falls through to its `default:` branch and returns the meta capability verbatim rather than mapping it down to primitives, so a post's own author can lose edit access to it. Subtraction cannot work against a shared registry, as nothing records which post types an entry is owed to. Replace it with `_rebuild_post_type_meta_capabilities()`, which rebuilds the registry from the post types that remain, and call that from `unregister_post_type()` once the post type has been removed from `$wp_post_types`. Removing the loop also empties the `foreach.nonIterable` PHPStan baseline, so that file and its `includes` entry are deleted. The `@ticket` annotations on the new tests are placeholders pending the Trac ticket number. Co-Authored-By: Claude Opus 5 (1M context) --- phpstan.neon.dist | 1 - src/wp-includes/class-wp-post-type.php | 12 +- src/wp-includes/post.php | 29 ++++ .../baselines/foreach.nonIterable.neon | 25 --- tests/phpunit/tests/post/types.php | 156 ++++++++++++++++++ 5 files changed, 188 insertions(+), 35 deletions(-) delete mode 100644 tests/phpstan/baselines/foreach.nonIterable.neon diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 9f53887d400de..c20752eef5d89 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -42,7 +42,6 @@ includes: - tests/phpstan/baselines/empty.offset.neon - tests/phpstan/baselines/empty.property.neon - tests/phpstan/baselines/empty.variable.neon - - tests/phpstan/baselines/foreach.nonIterable.neon - tests/phpstan/baselines/function.alreadyNarrowedType.neon - tests/phpstan/baselines/function.impossibleType.neon - tests/phpstan/baselines/function.resultUnused.neon diff --git a/src/wp-includes/class-wp-post-type.php b/src/wp-includes/class-wp-post-type.php index b37e84768d9f7..615fa104ca608 100644 --- a/src/wp-includes/class-wp-post-type.php +++ b/src/wp-includes/class-wp-post-type.php @@ -798,12 +798,11 @@ public function remove_supports() { * * @since 4.6.0 * - * @global WP_Rewrite $wp_rewrite WordPress rewrite component. - * @global WP $wp Current WordPress environment instance. - * @global array $post_type_meta_caps Used to remove meta capabilities. + * @global WP_Rewrite $wp_rewrite WordPress rewrite component. + * @global WP $wp Current WordPress environment instance. */ public function remove_rewrite_rules() { - global $wp, $wp_rewrite, $post_type_meta_caps; + global $wp, $wp_rewrite; // Remove query var. if ( false !== $this->query_var ) { @@ -820,11 +819,6 @@ public function remove_rewrite_rules() { } } } - - // Remove registered custom meta capabilities. - foreach ( $this->cap as $cap ) { - unset( $post_type_meta_caps[ $cap ] ); - } } /** diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 3f73e5463dbfe..23f3e32b4c15a 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -1944,6 +1944,9 @@ function unregister_post_type( $post_type ) { unset( $wp_post_types[ $post_type ] ); + // Rebuild the meta capabilities registry now that the post type is gone. + _rebuild_post_type_meta_capabilities(); + /** * Fires after a post type was unregistered. * @@ -2100,6 +2103,32 @@ function _post_type_meta_capabilities( $capabilities = null ) { } } +/** + * Rebuilds the list of post type meta caps for map_meta_cap() from the registered post types. + * + * The meta capabilities stored by _post_type_meta_capabilities() are keyed by the custom + * capability name, so a single entry may be owed to any number of registered post types. + * Unregistering one of them therefore cannot simply remove that post type's entries, as + * other post types may still depend on them. The list is rebuilt from scratch instead. + * + * @since 7.2.0 + * @access private + * + * @global array $post_type_meta_caps Used to store meta capabilities. + * @global array $wp_post_types List of post types. + */ +function _rebuild_post_type_meta_capabilities(): void { + global $post_type_meta_caps, $wp_post_types; + + $post_type_meta_caps = array(); + + foreach ( $wp_post_types as $post_type_object ) { + if ( $post_type_object->map_meta_cap ) { + _post_type_meta_capabilities( array_filter( get_object_vars( $post_type_object->cap ), 'is_string' ) ); + } + } +} + /** * Builds an object with all post type labels out of a post type object. * diff --git a/tests/phpstan/baselines/foreach.nonIterable.neon b/tests/phpstan/baselines/foreach.nonIterable.neon deleted file mode 100644 index be8bba17a113c..0000000000000 --- a/tests/phpstan/baselines/foreach.nonIterable.neon +++ /dev/null @@ -1,25 +0,0 @@ -# PHPStan baseline for the `foreach.nonIterable` errors in WordPress core. -# -# https://phpstan.org/error-identifiers/foreach.nonIterable -# -# Each entry is scoped to a single file and carries an exact occurrence count, -# so that a new instance is reported as a new error rather than being absorbed -# silently. Fixing an occurrence therefore means decrementing or removing its -# entry here as part of the same change. -# -# The goal is to empty this file and delete it, along with the `includes` entry -# for it in phpstan.neon.dist. -# -# Generated by `composer phpstan:baselines`. Do not edit by hand; regenerate with -# -# composer phpstan:baselines -- --identifier=foreach.nonIterable -# -# which reruns the analysis with this file suppressed so the errors surface again. - -parameters: - ignoreErrors: - - - message: '#^Argument of an invalid type stdClass supplied for foreach, only iterables are supported\.$#' - identifier: foreach.nonIterable - count: 1 - path: ../../../src/wp-includes/class-wp-post-type.php diff --git a/tests/phpunit/tests/post/types.php b/tests/phpunit/tests/post/types.php index 5ae45c67e1044..15eda0fb15c58 100644 --- a/tests/phpunit/tests/post/types.php +++ b/tests/phpunit/tests/post/types.php @@ -13,6 +13,22 @@ class Tests_Post_Types extends WP_UnitTestCase { */ public $post_type; + /** + * Author user ID. + * + * @var int + */ + public static $author_id; + + /** + * Sets up shared fixtures. + * + * @param WP_UnitTest_Factory $factory Factory instance. + */ + public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { + self::$author_id = $factory->user->create( array( 'role' => 'author' ) ); + } + /** * Set up. * @@ -419,6 +435,146 @@ public function test_unregister_post_type_removes_custom_meta_capabilities() { $this->assertArrayNotHasKey( 'edit_bar', $post_type_meta_caps ); } + /** + * Tests that meta capabilities shared with another registered post type are retained. + * + * Meta capabilities are stored keyed by the custom capability name, so post types + * sharing a capability type resolve to the same entries. Unregistering one of them + * must not remove the entries the others still rely on. + * + * @ticket 00000 + * + * @global array $post_type_meta_caps Used to store meta capabilities. + */ + public function test_unregister_post_type_retains_meta_capabilities_shared_with_another_post_type() { + global $post_type_meta_caps; + + $args = array( + 'public' => true, + 'capability_type' => 'publication', + 'map_meta_cap' => true, + ); + + register_post_type( 'book', $args ); + register_post_type( 'magazine', $args ); + + $this->assertSame( 'read_post', $post_type_meta_caps['read_publication'], 'The read meta capability was not registered.' ); + $this->assertSame( 'delete_post', $post_type_meta_caps['delete_publication'], 'The delete meta capability was not registered.' ); + $this->assertSame( 'edit_post', $post_type_meta_caps['edit_publication'], 'The edit meta capability was not registered.' ); + + $this->assertTrue( unregister_post_type( 'book' ) ); + + $this->assertSame( 'read_post', $post_type_meta_caps['read_publication'], 'The read meta capability of the remaining post type was removed.' ); + $this->assertSame( 'delete_post', $post_type_meta_caps['delete_publication'], 'The delete meta capability of the remaining post type was removed.' ); + $this->assertSame( 'edit_post', $post_type_meta_caps['edit_publication'], 'The edit meta capability of the remaining post type was removed.' ); + } + + /** + * Tests that a remaining post type's meta capabilities still map down to primitive capabilities. + * + * @ticket 00000 + */ + public function test_unregister_post_type_retains_meta_capability_mapping_for_another_post_type() { + $args = array( + 'public' => true, + 'capability_type' => 'publication', + 'map_meta_cap' => true, + ); + + register_post_type( 'book', $args ); + register_post_type( 'magazine', $args ); + + $post_id = self::factory()->post->create( + array( + 'post_type' => 'magazine', + 'post_status' => 'publish', + 'post_author' => self::$author_id, + ) + ); + + $this->assertSame( + array( 'edit_published_publications' ), + map_meta_cap( 'edit_publication', self::$author_id, $post_id ), + 'The meta capability did not map to a primitive capability.' + ); + + $this->assertTrue( unregister_post_type( 'book' ) ); + + $this->assertSame( + array( 'edit_published_publications' ), + map_meta_cap( 'edit_publication', self::$author_id, $post_id ), + 'The meta capability of the remaining post type no longer maps to a primitive capability.' + ); + } + + /** + * Tests that a post type which does not map meta capabilities removes none on unregistration. + * + * Such a post type never stores any meta capabilities, so it must not remove the + * identically named entries belonging to the built-in post types. + * + * @ticket 00000 + * + * @global array $post_type_meta_caps Used to store meta capabilities. + */ + public function test_unregister_post_type_retains_meta_capabilities_when_not_mapping_meta_caps() { + global $post_type_meta_caps; + + register_post_type( + 'foo', + array( + 'public' => true, + 'map_meta_cap' => false, + ) + ); + + $this->assertTrue( unregister_post_type( 'foo' ) ); + + $this->assertSame( 'read_post', $post_type_meta_caps['read_post'], 'The built-in read meta capability was removed.' ); + $this->assertSame( 'delete_post', $post_type_meta_caps['delete_post'], 'The built-in delete meta capability was removed.' ); + $this->assertSame( 'edit_post', $post_type_meta_caps['edit_post'], 'The built-in edit meta capability was removed.' ); + } + + /** + * Tests that primitive capabilities are not treated as meta capabilities on unregistration. + * + * Only the read, delete and edit capabilities are stored as meta capabilities. A post type + * using one of those names as a primitive capability must not remove another post type's + * meta capability of the same name. + * + * @ticket 00000 + * + * @global array $post_type_meta_caps Used to store meta capabilities. + */ + public function test_unregister_post_type_retains_meta_capabilities_matching_primitive_capabilities() { + global $post_type_meta_caps; + + register_post_type( + 'book', + array( + 'public' => true, + 'capability_type' => 'book', + 'map_meta_cap' => true, + ) + ); + + // For this post type 'edit_book' is a primitive capability, not a meta capability. + register_post_type( + 'shelf', + array( + 'public' => true, + 'map_meta_cap' => false, + 'capabilities' => array( + 'edit_posts' => 'edit_book', + ), + ) + ); + + $this->assertTrue( unregister_post_type( 'shelf' ) ); + + $this->assertSame( 'edit_post', $post_type_meta_caps['edit_book'], 'The meta capability of another post type was removed.' ); + } + /** * @ticket 14761 */ From 4bb60a6d69bc05645985673aa8f76dcae857beda Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sat, 29 Aug 2026 20:48:43 -0700 Subject: [PATCH 02/11] Tidy the `_post_type_meta_capabilities()` signature and documentation When introduced in 3.1 the function held its list in a `static $meta_caps` and returned it when called with no arguments: function _post_type_meta_capabilities( $capabilities = null ) { static $meta_caps = array(); if ( null === $capabilities ) return $meta_caps; That branch went away when the list moved to the `$post_type_meta_caps` global, but the `null` default and the "Stores or returns" summary were left behind. Calling the function with no arguments has since only produced a `foreach()` warning, so default the parameter to an empty array, drop the stale half of the summary, and declare the `void` return. Also give the `$post_type_meta_caps` global a value type, matching how it is documented on `_rebuild_post_type_meta_capabilities()`. Its keys are custom capability names and its values core meta capability names, so it is an `array` rather than a bare `array`. Co-Authored-By: Claude Opus 5 (1M context) --- src/wp-includes/post.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 23f3e32b4c15a..bb9d2ac398ae2 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2084,16 +2084,16 @@ function get_post_type_capabilities( $args ) { } /** - * Stores or returns a list of post type meta caps for map_meta_cap(). + * Stores a list of post type meta caps for map_meta_cap(). * * @since 3.1.0 * @access private * - * @global array $post_type_meta_caps Used to store meta capabilities. + * @global array $post_type_meta_caps Used to store meta capabilities. * * @param string[] $capabilities Post type meta capabilities. */ -function _post_type_meta_capabilities( $capabilities = null ) { +function _post_type_meta_capabilities( $capabilities = array() ): void { global $post_type_meta_caps; foreach ( $capabilities as $core => $custom ) { From 8f09def26bacfad771a62846ce49d2a3899f744f Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sat, 29 Aug 2026 22:52:53 -0700 Subject: [PATCH 03/11] Document `_post_type_meta_capabilities()` as taking a capability map The function's `foreach ( $capabilities as $core => $custom )` shows what it expects: a map of core meta capability name to the custom capability name it is registered under. `get_post_type_capabilities()` passes exactly that, a merge of `$default_capabilities` and, optionally, `$default_capabilities_for_mapping`, both of which are string to string. The `string[]` annotation left the keys untyped and so said none of this. Document the parameter as `array`, matching the `$post_type_meta_caps` global it populates. `_rebuild_post_type_meta_capabilities()` sources its map from `WP_Post_Type::$cap`. That is a `stdClass`, which carries no property types, and PHPStan types `get_object_vars()` on any object as `array` regardless of how the object itself is documented. The call is therefore reported as an `argument.type` error at higher rule levels even though the values are always strings at runtime. Accept that rather than narrowing the map at runtime or relaxing what the function documents; typing `$cap` well enough for the analyser to follow is a separate piece of work. Co-Authored-By: Claude Opus 5 (1M context) --- src/wp-includes/post.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index bb9d2ac398ae2..c7c711216bd64 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2091,7 +2091,8 @@ function get_post_type_capabilities( $args ) { * * @global array $post_type_meta_caps Used to store meta capabilities. * - * @param string[] $capabilities Post type meta capabilities. + * @param array $capabilities Map of core meta capability name to the custom + * capability name it is registered under. */ function _post_type_meta_capabilities( $capabilities = array() ): void { global $post_type_meta_caps; @@ -2124,7 +2125,7 @@ function _rebuild_post_type_meta_capabilities(): void { foreach ( $wp_post_types as $post_type_object ) { if ( $post_type_object->map_meta_cap ) { - _post_type_meta_capabilities( array_filter( get_object_vars( $post_type_object->cap ), 'is_string' ) ); + _post_type_meta_capabilities( get_object_vars( $post_type_object->cap ) ); } } } From 5a95164a5eb002e48c3dec8808f7d585568c5ce0 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sat, 29 Aug 2026 22:58:47 -0700 Subject: [PATCH 04/11] Eliminate redundant call to post_type_exists() by reusing return value of get_post_type_object() to fix PHPStan errors Fixes: Cannot access property on WP_Post_Type|null. --- src/wp-includes/post.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index c7c711216bd64..916623c8f45be 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -1925,12 +1925,11 @@ function register_post_type( $post_type, $args = array() ) { function unregister_post_type( $post_type ) { global $wp_post_types; - if ( ! post_type_exists( $post_type ) ) { + $post_type_object = get_post_type_object( $post_type ); + if ( ! $post_type_object ) { return new WP_Error( 'invalid_post_type', __( 'Invalid post type.' ) ); } - $post_type_object = get_post_type_object( $post_type ); - // Do not allow unregistering internal post types. if ( $post_type_object->_builtin ) { return new WP_Error( 'invalid_post_type', __( 'Unregistering a built-in post type is not allowed' ) ); From d2e3f4c86d1c3e68affe4e5fcda987707b687b7f Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sat, 29 Aug 2026 22:59:22 -0700 Subject: [PATCH 05/11] Add missing type information for $wp_post_types global --- src/wp-includes/post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 916623c8f45be..7f69bacdcce94 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -1917,7 +1917,7 @@ function register_post_type( $post_type, $args = array() ) { * * @since 4.5.0 * - * @global array $wp_post_types List of post types. + * @global array $wp_post_types List of post types. * * @param string $post_type Post type to unregister. * @return true|WP_Error True on success, WP_Error on failure or if the post type doesn't exist. From 6c45343bf90622a58e6e4c67919d545ca69c40b8 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sat, 29 Aug 2026 23:02:38 -0700 Subject: [PATCH 06/11] Inline the meta capability rebuild into `unregister_post_type()` `_rebuild_post_type_meta_capabilities()` had a single caller and existed only to hold a few lines that read no state the caller did not already have. Move its body into `unregister_post_type()`, directly after the post type is removed from `$wp_post_types`, and drop the function. The loop variable is named `$registered_post_type` because `$post_type_object` is already bound to the post type being unregistered. Co-Authored-By: Claude Opus 5 (1M context) --- src/wp-includes/post.php | 47 +++++++++++++++------------------------- 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 7f69bacdcce94..4f7294864d196 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -1917,13 +1917,14 @@ function register_post_type( $post_type, $args = array() ) { * * @since 4.5.0 * - * @global array $wp_post_types List of post types. + * @global array $post_type_meta_caps Used to store meta capabilities. + * @global array $wp_post_types List of post types. * * @param string $post_type Post type to unregister. * @return true|WP_Error True on success, WP_Error on failure or if the post type doesn't exist. */ function unregister_post_type( $post_type ) { - global $wp_post_types; + global $post_type_meta_caps, $wp_post_types; $post_type_object = get_post_type_object( $post_type ); if ( ! $post_type_object ) { @@ -1943,8 +1944,20 @@ function unregister_post_type( $post_type ) { unset( $wp_post_types[ $post_type ] ); - // Rebuild the meta capabilities registry now that the post type is gone. - _rebuild_post_type_meta_capabilities(); + /* + * Rebuild the meta capabilities of the post types that remain. + * + * They are keyed by the custom capability name, so a single entry may be owed to any + * number of registered post types. Removing the entries for this post type alone could + * therefore remove entries that the others still depend on. + */ + $post_type_meta_caps = array(); + + foreach ( $wp_post_types as $registered_post_type ) { + if ( $registered_post_type->map_meta_cap ) { + _post_type_meta_capabilities( get_object_vars( $registered_post_type->cap ) ); + } + } /** * Fires after a post type was unregistered. @@ -2103,32 +2116,6 @@ function _post_type_meta_capabilities( $capabilities = array() ): void { } } -/** - * Rebuilds the list of post type meta caps for map_meta_cap() from the registered post types. - * - * The meta capabilities stored by _post_type_meta_capabilities() are keyed by the custom - * capability name, so a single entry may be owed to any number of registered post types. - * Unregistering one of them therefore cannot simply remove that post type's entries, as - * other post types may still depend on them. The list is rebuilt from scratch instead. - * - * @since 7.2.0 - * @access private - * - * @global array $post_type_meta_caps Used to store meta capabilities. - * @global array $wp_post_types List of post types. - */ -function _rebuild_post_type_meta_capabilities(): void { - global $post_type_meta_caps, $wp_post_types; - - $post_type_meta_caps = array(); - - foreach ( $wp_post_types as $post_type_object ) { - if ( $post_type_object->map_meta_cap ) { - _post_type_meta_capabilities( get_object_vars( $post_type_object->cap ) ); - } - } -} - /** * Builds an object with all post type labels out of a post type object. * From ad8cb2ac90f36ec98d5dce55115bf1edd2c44bf6 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sat, 29 Aug 2026 23:06:37 -0700 Subject: [PATCH 07/11] Remove unnecessary line break --- src/wp-includes/post.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 4f7294864d196..8206ed0b4f278 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -1952,7 +1952,6 @@ function unregister_post_type( $post_type ) { * therefore remove entries that the others still depend on. */ $post_type_meta_caps = array(); - foreach ( $wp_post_types as $registered_post_type ) { if ( $registered_post_type->map_meta_cap ) { _post_type_meta_capabilities( get_object_vars( $registered_post_type->cap ) ); From a47931bc85d2626432a421d1e4fce8e8968672fb Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sat, 29 Aug 2026 23:09:27 -0700 Subject: [PATCH 08/11] Record the `_post_type_meta_capabilities()` history in its docblock r36316 moved the list of meta capabilities from a `static $meta_caps` into the `$post_type_meta_caps` global, which removed the branch that returned the list when the function was called without arguments: static $meta_caps = array(); if ( null === $capabilities ) return $meta_caps; The docblock was never updated to say so, and until this branch changed it the summary still described the function as returning that list. Add the missing `@since 4.5.0` entry for that, along with one for the parameter default changing from `null` to an empty array. Also reference `map_meta_cap()` with an inline `@see`, since that is what consumes the stored list. Co-Authored-By: Claude Opus 5 (1M context) --- src/wp-includes/post.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 8206ed0b4f278..646ca4bb92acb 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2095,9 +2095,12 @@ function get_post_type_capabilities( $args ) { } /** - * Stores a list of post type meta caps for map_meta_cap(). + * Stores a list of post type meta caps for {@see map_meta_cap()}. * * @since 3.1.0 + * @since 4.5.0 The list moved to the `$post_type_meta_caps` global and the function + * no longer returns it when called without arguments. + * @since 7.2.0 The `$capabilities` parameter defaults to an empty array rather than `null`. * @access private * * @global array $post_type_meta_caps Used to store meta capabilities. From e766cb048a48a3d099e7ce418e9d1a01e3d08a54 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sat, 29 Aug 2026 23:10:48 -0700 Subject: [PATCH 09/11] Use native type hint for property --- tests/phpunit/tests/post/types.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/phpunit/tests/post/types.php b/tests/phpunit/tests/post/types.php index 15eda0fb15c58..3c4adabe0443b 100644 --- a/tests/phpunit/tests/post/types.php +++ b/tests/phpunit/tests/post/types.php @@ -15,10 +15,8 @@ class Tests_Post_Types extends WP_UnitTestCase { /** * Author user ID. - * - * @var int */ - public static $author_id; + public static int $author_id; /** * Sets up shared fixtures. From da2cfe23006a956be3e074b3263519d3f27cbd31 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sun, 30 Aug 2026 16:15:32 -0700 Subject: [PATCH 10/11] Note the moved capability cleanup in `remove_rewrite_rules()` The method no longer touches the meta capability list, so record that with a `@since` entry and point at `unregister_post_type()`, which now rebuilds the list from the post types that remain. Described as a rebuild rather than a removal, since the work did not simply move to the caller. Nothing unsets entries any more, and a reader sent looking for one would not find it. Co-Authored-By: Claude Opus 5 (1M context) --- src/wp-includes/class-wp-post-type.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wp-includes/class-wp-post-type.php b/src/wp-includes/class-wp-post-type.php index 615fa104ca608..88fecd08c868c 100644 --- a/src/wp-includes/class-wp-post-type.php +++ b/src/wp-includes/class-wp-post-type.php @@ -797,6 +797,8 @@ public function remove_supports() { * Removes any rewrite rules, permastructs, and rules for the post type. * * @since 4.6.0 + * @since 7.2.0 Registered meta capabilities are no longer removed here. They are rebuilt + * from the post types that remain by {@see unregister_post_type()}. * * @global WP_Rewrite $wp_rewrite WordPress rewrite component. * @global WP $wp Current WordPress environment instance. From 1a5aa5f91088c610c5ba98102a5d47bd66a79d43 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 31 Aug 2026 12:37:25 -0700 Subject: [PATCH 11/11] Reference the Trac ticket from the new tests The four tests covering the meta capability rebuild carried a placeholder `@ticket` number while the ticket was still being drafted. Point them at the ticket that was filed. Co-Authored-By: Claude Opus 5 (1M context) --- tests/phpunit/tests/post/types.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/phpunit/tests/post/types.php b/tests/phpunit/tests/post/types.php index 3c4adabe0443b..5cfeaf4785396 100644 --- a/tests/phpunit/tests/post/types.php +++ b/tests/phpunit/tests/post/types.php @@ -440,7 +440,7 @@ public function test_unregister_post_type_removes_custom_meta_capabilities() { * sharing a capability type resolve to the same entries. Unregistering one of them * must not remove the entries the others still rely on. * - * @ticket 00000 + * @ticket 66008 * * @global array $post_type_meta_caps Used to store meta capabilities. */ @@ -470,7 +470,7 @@ public function test_unregister_post_type_retains_meta_capabilities_shared_with_ /** * Tests that a remaining post type's meta capabilities still map down to primitive capabilities. * - * @ticket 00000 + * @ticket 66008 */ public function test_unregister_post_type_retains_meta_capability_mapping_for_another_post_type() { $args = array( @@ -511,7 +511,7 @@ public function test_unregister_post_type_retains_meta_capability_mapping_for_an * Such a post type never stores any meta capabilities, so it must not remove the * identically named entries belonging to the built-in post types. * - * @ticket 00000 + * @ticket 66008 * * @global array $post_type_meta_caps Used to store meta capabilities. */ @@ -540,7 +540,7 @@ public function test_unregister_post_type_retains_meta_capabilities_when_not_map * using one of those names as a primitive capability must not remove another post type's * meta capability of the same name. * - * @ticket 00000 + * @ticket 66008 * * @global array $post_type_meta_caps Used to store meta capabilities. */