From f3c1d9216353f2df100b85df6f4d465aa54f7333 Mon Sep 17 00:00:00 2001 From: Joseph Scott Date: Fri, 9 Jan 2026 11:37:53 -0700 Subject: [PATCH 1/4] get_post_states: post type checks https://core.trac.wordpress.org/ticket/58932 The function `get_post_states()` requires a WP_Post object as an arg. But `get_post()` can return `null` - so we need multiple layers of defense here. First, `get_post_states()` needs to validate that it has a WP_Post object before trying to treat $post as an object. Second, the call to `get_post_states()` in `wp-includes/nav-menu.php` needs to be conditional on `get_post()` returning a WP_Post object. This also includes a new test for `get_post_states()` to make sure it does the right thing when given something other than a WP_Post object. --- src/wp-admin/includes/template.php | 4 ++++ src/wp-includes/nav-menu.php | 10 ++++++---- tests/phpunit/tests/admin/includesTemplate.php | 17 +++++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/wp-admin/includes/template.php b/src/wp-admin/includes/template.php index 2eaf67454394e..ba4ecdc6d3ed4 100644 --- a/src/wp-admin/includes/template.php +++ b/src/wp-admin/includes/template.php @@ -2297,6 +2297,10 @@ function _post_states( $post, $display = true ) { function get_post_states( $post ) { $post_states = array(); + if ( ! $post instanceof WP_Post ) { + return $post_states; + } + if ( isset( $_REQUEST['post_status'] ) ) { $post_status = $_REQUEST['post_status']; } else { diff --git a/src/wp-includes/nav-menu.php b/src/wp-includes/nav-menu.php index 83a67cfe2a800..dc6de5c70c662 100644 --- a/src/wp-includes/nav-menu.php +++ b/src/wp-includes/nav-menu.php @@ -875,10 +875,12 @@ function wp_setup_nav_menu_item( $menu_item ) { $menu_item->type_label = $object->labels->singular_name; // Denote post states for special pages (only in the admin). if ( function_exists( 'get_post_states' ) ) { - $menu_post = get_post( $menu_item->object_id ); - $post_states = get_post_states( $menu_post ); - if ( $post_states ) { - $menu_item->type_label = wp_strip_all_tags( implode( ', ', $post_states ) ); + $menu_post = get_post( $menu_item->object_id ); + if ( $menu_post instanceof WP_Post ) { + $post_states = get_post_states( $menu_post ); + if ( $post_states ) { + $menu_item->type_label = wp_strip_all_tags( implode( ', ', $post_states ) ); + } } } } else { diff --git a/tests/phpunit/tests/admin/includesTemplate.php b/tests/phpunit/tests/admin/includesTemplate.php index 909aff217a583..436659179dd26 100644 --- a/tests/phpunit/tests/admin/includesTemplate.php +++ b/tests/phpunit/tests/admin/includesTemplate.php @@ -494,4 +494,21 @@ public function test_wp_add_dashboard_widget() { // This doesn't actually get removed due to the invalid priority. remove_meta_box( 'dashboard2', 'dashboard', 'normal' ); } + + /** + * Tests that get_post_states() handles a null value gracefully. + * + * This can happen when get_post() returns null (e.g., when a post + * doesn't exist) and that result is passed to get_post_states() + * without being checked first. + * + * @ticket 58932 + * + * @covers ::get_post_states + */ + public function test_get_post_states_with_null_returns_empty_array() { + $result = get_post_states( null ); + $this->assertIsArray( $result, 'get_post_states() should return an array when passed null.' ); + $this->assertEmpty( $result, 'get_post_states() should return an empty array when passed null.' ); + } } From db3d692db167855db878157d248da79d23cd8921 Mon Sep 17 00:00:00 2001 From: Joseph Scott Date: Fri, 9 Jan 2026 20:57:12 -0700 Subject: [PATCH 2/4] Update $post type to WP_Post|mixed --- src/wp-admin/includes/template.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-admin/includes/template.php b/src/wp-admin/includes/template.php index ba4ecdc6d3ed4..aad286edffe8d 100644 --- a/src/wp-admin/includes/template.php +++ b/src/wp-admin/includes/template.php @@ -2291,7 +2291,7 @@ function _post_states( $post, $display = true ) { * * @since 5.3.0 * - * @param WP_Post $post The post to retrieve states for. + * @param WP_Post|mixed $post The post to retrieve states for. * @return string[] Array of post state labels keyed by their state. */ function get_post_states( $post ) { From d6e0ea71584d4f4d58c144187104c52d1970e4af Mon Sep 17 00:00:00 2001 From: Joseph Scott Date: Fri, 9 Jan 2026 22:22:22 -0700 Subject: [PATCH 3/4] Revert types in docblock --- src/wp-admin/includes/template.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-admin/includes/template.php b/src/wp-admin/includes/template.php index aad286edffe8d..ba4ecdc6d3ed4 100644 --- a/src/wp-admin/includes/template.php +++ b/src/wp-admin/includes/template.php @@ -2291,7 +2291,7 @@ function _post_states( $post, $display = true ) { * * @since 5.3.0 * - * @param WP_Post|mixed $post The post to retrieve states for. + * @param WP_Post $post The post to retrieve states for. * @return string[] Array of post state labels keyed by their state. */ function get_post_states( $post ) { From b3e1f99261f65b34cef197c1241660d9b36f59a8 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 9 Jan 2026 22:33:29 -0800 Subject: [PATCH 4/4] Simplify test assertions --- tests/phpunit/tests/admin/includesTemplate.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/phpunit/tests/admin/includesTemplate.php b/tests/phpunit/tests/admin/includesTemplate.php index 436659179dd26..15a5edd672f09 100644 --- a/tests/phpunit/tests/admin/includesTemplate.php +++ b/tests/phpunit/tests/admin/includesTemplate.php @@ -508,7 +508,6 @@ public function test_wp_add_dashboard_widget() { */ public function test_get_post_states_with_null_returns_empty_array() { $result = get_post_states( null ); - $this->assertIsArray( $result, 'get_post_states() should return an array when passed null.' ); - $this->assertEmpty( $result, 'get_post_states() should return an empty array when passed null.' ); + $this->assertSame( array(), $result, 'get_post_states() should return an empty array when WP_Post is not supplied.' ); } }