From 9243e60d5852b6f5733d6dc788454a59c0d6f992 Mon Sep 17 00:00:00 2001 From: Aaron Jorbin <622599+aaronjorbin@users.noreply.github.com> Date: Fri, 16 Oct 2026 13:28:43 -0500 Subject: [PATCH 1/5] Don't fatal when invalid arguements are passed to wp_after_insert_post or after_delete_post. Trac Ticket: https://core.trac.wordpress.org/ticket/64498#comment Props bradshawtm --- src/wp-includes/query.php | 33 ++++++++++- .../tests/query/randomContentRedirect.php | 55 +++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/query.php b/src/wp-includes/query.php index 212ac1619e87f..fc41cd984122d 100644 --- a/src/wp-includes/query.php +++ b/src/wp-includes/query.php @@ -1534,6 +1534,9 @@ function wp_random_content_redirect(): void { * unpublished, renamed, moved or deleted. * * Runs on the {@see 'wp_after_insert_post'} and {@see 'after_delete_post'} actions. + * Third-party code may fire these actions with unexpected values, so the parameters are + * not type declared. Passing anything other than a `WP_Post` as `$post`, or anything other + * than a `WP_Post` or null as `$post_before`, is incorrect usage and the value is ignored. * * @since 7.2.0 * @@ -1545,9 +1548,37 @@ function wp_random_content_redirect(): void { * @param bool $update Optional. Whether this is an existing post being updated. Default false. * @param WP_Post|null $post_before Optional. Post object before the update, null for new posts. Default null. */ -function wp_random_content_flush_rewrite_rules_for_post( int $post_id, WP_Post $post, bool $update = false, ?WP_Post $post_before = null ): void { +function wp_random_content_flush_rewrite_rules_for_post( $post_id, $post, $update = false, $post_before = null ): void { global $wp_rewrite; + if ( ! $post instanceof WP_Post ) { + _doing_it_wrong( + __FUNCTION__, + sprintf( + /* translators: 1: $post, 2: WP_Post */ + __( 'The %1$s argument must be a %2$s object.' ), + '$post', + 'WP_Post' + ), + '7.2.0' + ); + return; + } + + if ( null !== $post_before && ! $post_before instanceof WP_Post ) { + _doing_it_wrong( + __FUNCTION__, + sprintf( + /* translators: 1: $post_before, 2: WP_Post */ + __( 'The %1$s argument must be a %2$s object or null.' ), + '$post_before', + 'WP_Post' + ), + '7.2.0' + ); + $post_before = null; + } + if ( ! $wp_rewrite->using_permalinks() || ! wp_is_random_content_redirect_enabled() ) { return; } diff --git a/tests/phpunit/tests/query/randomContentRedirect.php b/tests/phpunit/tests/query/randomContentRedirect.php index d8854593000f6..ae014ef404eef 100644 --- a/tests/phpunit/tests/query/randomContentRedirect.php +++ b/tests/phpunit/tests/query/randomContentRedirect.php @@ -1234,6 +1234,61 @@ public function test_saving_unrelated_pages_does_not_flush_rewrite_rules(): void $this->assertSame( 0, $flushes->get_call_count(), 'Only pages and posts at the random base should flush the rewrite rules.' ); } + /** + * @dataProvider data_hooks_fired_with_invalid_arguments + * + * @param string $hook The action to fire. + * @param array $args The arguments to fire it with. + */ + public function test_hooks_fired_with_invalid_arguments_do_not_flush_rewrite_rules( string $hook, array $args ): void { + $this->setExpectedIncorrectUsage( 'wp_random_content_flush_rewrite_rules_for_post' ); + + $flushes = new MockAction(); + add_filter( 'pre_update_option_rewrite_rules', array( $flushes, 'filter' ) ); + + do_action( $hook, ...$args ); + + $this->assertSame( 0, $flushes->get_call_count(), 'Firing the action with invalid arguments should not flush the rewrite rules.' ); + } + + /** + * Data provider. + * + * Core never fires these actions with invalid arguments, but third-party code can. + * Doing so is incorrect usage and must not cause a fatal error. + * + * @return array + */ + public function data_hooks_fired_with_invalid_arguments(): array { + return array( + 'wp_after_insert_post with nulls' => array( 'wp_after_insert_post', array( null, null, false, null ) ), + 'wp_after_insert_post with wrong types' => array( 'wp_after_insert_post', array( 'random', 'random', 'yes', 'random' ) ), + 'wp_after_insert_post with arrays' => array( 'wp_after_insert_post', array( array(), array( 'post_name' => 'random' ), 1, array() ) ), + 'after_delete_post with nulls' => array( 'after_delete_post', array( null, null ) ), + 'after_delete_post with wrong types' => array( 'after_delete_post', array( 'random', 'random' ) ), + ); + } + + public function test_page_named_random_still_flushes_rewrite_rules_when_the_other_arguments_are_invalid(): void { + $page = get_post( + $this->create_post( + array( + 'post_type' => 'page', + 'post_name' => 'random', + ) + ) + ); + + $this->setExpectedIncorrectUsage( 'wp_random_content_flush_rewrite_rules_for_post' ); + + $flushes = new MockAction(); + add_filter( 'pre_update_option_rewrite_rules', array( $flushes, 'filter' ) ); + + do_action( 'wp_after_insert_post', (string) $page->ID, $page, 'yes', false ); + + $this->assertSame( 1, $flushes->get_call_count(), 'A page at the random base should flush the rewrite rules even when the other arguments are invalid.' ); + } + /* * Opting out. */ From 3e2076af059abdd6e73ceade69b81df03fbf228e Mon Sep 17 00:00:00 2001 From: Aaron Jorbin <622599+aaronjorbin@users.noreply.github.com> Date: Fri, 16 Oct 2026 13:59:19 -0500 Subject: [PATCH 2/5] Refactor test for invalid arguments to improve clarity and maintainability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cause: multisite hooks _update_posts_count_on_delete() onto after_delete_post too (src/wp-includes/ms-default-filters.php:78). It reads $post->post_status on string 'random' → warning → test error. Not our function's bug. Other core callbacks choking on junk are out of scope. Fix: tests now call wp_random_content_flush_rewrite_rules_for_post() directly instead of do_action(). Isolates unit under test from unrelated callbacks on same hook. Data provider renamed data_invalid_arguments, test test_invalid_arguments_do_not_flush_rewrite_rules. Same five cases, now keyed by value shape rather than hook name. _doing_it_wrong() still asserted on every case. --- .../tests/query/randomContentRedirect.php | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/phpunit/tests/query/randomContentRedirect.php b/tests/phpunit/tests/query/randomContentRedirect.php index ae014ef404eef..71a98479484d7 100644 --- a/tests/phpunit/tests/query/randomContentRedirect.php +++ b/tests/phpunit/tests/query/randomContentRedirect.php @@ -1235,37 +1235,37 @@ public function test_saving_unrelated_pages_does_not_flush_rewrite_rules(): void } /** - * @dataProvider data_hooks_fired_with_invalid_arguments + * @dataProvider data_invalid_arguments * - * @param string $hook The action to fire. - * @param array $args The arguments to fire it with. + * @param array $args The arguments to call the function with. */ - public function test_hooks_fired_with_invalid_arguments_do_not_flush_rewrite_rules( string $hook, array $args ): void { + public function test_invalid_arguments_do_not_flush_rewrite_rules( array $args ): void { $this->setExpectedIncorrectUsage( 'wp_random_content_flush_rewrite_rules_for_post' ); $flushes = new MockAction(); add_filter( 'pre_update_option_rewrite_rules', array( $flushes, 'filter' ) ); - do_action( $hook, ...$args ); + wp_random_content_flush_rewrite_rules_for_post( ...$args ); - $this->assertSame( 0, $flushes->get_call_count(), 'Firing the action with invalid arguments should not flush the rewrite rules.' ); + $this->assertSame( 0, $flushes->get_call_count(), 'Invalid arguments should not flush the rewrite rules.' ); } /** * Data provider. * - * Core never fires these actions with invalid arguments, but third-party code can. - * Doing so is incorrect usage and must not cause a fatal error. + * Core never fires the `wp_after_insert_post` and `after_delete_post` actions with invalid + * arguments, but third-party code can. Doing so is incorrect usage and must not cause a + * fatal error. * - * @return array + * @return array */ - public function data_hooks_fired_with_invalid_arguments(): array { + public function data_invalid_arguments(): array { return array( - 'wp_after_insert_post with nulls' => array( 'wp_after_insert_post', array( null, null, false, null ) ), - 'wp_after_insert_post with wrong types' => array( 'wp_after_insert_post', array( 'random', 'random', 'yes', 'random' ) ), - 'wp_after_insert_post with arrays' => array( 'wp_after_insert_post', array( array(), array( 'post_name' => 'random' ), 1, array() ) ), - 'after_delete_post with nulls' => array( 'after_delete_post', array( null, null ) ), - 'after_delete_post with wrong types' => array( 'after_delete_post', array( 'random', 'random' ) ), + 'nulls' => array( array( null, null, false, null ) ), + 'strings' => array( array( 'random', 'random', 'yes', 'random' ) ), + 'arrays' => array( array( array(), array( 'post_name' => 'random' ), 1, array() ) ), + 'nulls without optional values' => array( array( null, null ) ), + 'strings without optional values' => array( array( 'random', 'random' ) ), ); } @@ -1284,7 +1284,7 @@ public function test_page_named_random_still_flushes_rewrite_rules_when_the_othe $flushes = new MockAction(); add_filter( 'pre_update_option_rewrite_rules', array( $flushes, 'filter' ) ); - do_action( 'wp_after_insert_post', (string) $page->ID, $page, 'yes', false ); + wp_random_content_flush_rewrite_rules_for_post( (string) $page->ID, $page, 'yes', false ); $this->assertSame( 1, $flushes->get_call_count(), 'A page at the random base should flush the rewrite rules even when the other arguments are invalid.' ); } From 4f2693e9a5c92487bb9cb04720f57436730a8c1a Mon Sep 17 00:00:00 2001 From: Aaron Jorbin <622599+aaronjorbin@users.noreply.github.com> Date: Mon, 19 Oct 2026 15:17:18 -0500 Subject: [PATCH 3/5] Enhance wp_random_content_flush_rewrite_rules_for_post to handle optional post parameter and improve error handling for invalid arguments --- src/wp-includes/ms-blogs.php | 7 +- src/wp-includes/query.php | 51 +++-- .../tests/query/randomContentRedirect.php | 182 ++++++++++++++++-- 3 files changed, 203 insertions(+), 37 deletions(-) diff --git a/src/wp-includes/ms-blogs.php b/src/wp-includes/ms-blogs.php index adb20da2678bc..117eaf6ef6fd6 100644 --- a/src/wp-includes/ms-blogs.php +++ b/src/wp-includes/ms-blogs.php @@ -889,11 +889,12 @@ function _update_blog_date_on_post_delete( $post_id ) { * * @since 4.0.0 * @since 6.2.0 Added the `$post` parameter. + * @since 7.2.0 The `$post` parameter isn't always guaranteed to be provided. * - * @param int $post_id Post ID. - * @param WP_Post $post Post object. + * @param int $post_id Post ID. + * @param WP_Post|null $post Optional. Post object. Default null. */ -function _update_posts_count_on_delete( $post_id, $post ) { +function _update_posts_count_on_delete( $post_id, $post = null ) { if ( ! $post || 'publish' !== $post->post_status || 'post' !== $post->post_type ) { return; } diff --git a/src/wp-includes/query.php b/src/wp-includes/query.php index fc41cd984122d..f53cd05405f97 100644 --- a/src/wp-includes/query.php +++ b/src/wp-includes/query.php @@ -1535,8 +1535,11 @@ function wp_random_content_redirect(): void { * * Runs on the {@see 'wp_after_insert_post'} and {@see 'after_delete_post'} actions. * Third-party code may fire these actions with unexpected values, so the parameters are - * not type declared. Passing anything other than a `WP_Post` as `$post`, or anything other - * than a `WP_Post` or null as `$post_before`, is incorrect usage and the value is ignored. + * not type declared. When `$post` is omitted it is looked up from `$post_id`. Passing + * anything other than a `WP_Post` as `$post`, or a post that cannot be looked up, is + * incorrect usage and nothing is flushed. `$post_before` is only considered on + * `wp_after_insert_post`, where passing anything other than a `WP_Post` or null is + * incorrect usage and the value is ignored. * * @since 7.2.0 * @@ -1544,13 +1547,18 @@ function wp_random_content_redirect(): void { * @global WP_Rewrite $wp_rewrite WordPress rewrite component. * * @param int $post_id Post ID. - * @param WP_Post $post Post object. + * @param WP_Post|null $post Optional. Post object. Looked up from `$post_id` when null. Default null. * @param bool $update Optional. Whether this is an existing post being updated. Default false. * @param WP_Post|null $post_before Optional. Post object before the update, null for new posts. Default null. */ -function wp_random_content_flush_rewrite_rules_for_post( $post_id, $post, $update = false, $post_before = null ): void { +function wp_random_content_flush_rewrite_rules_for_post( $post_id, $post = null, $update = false, $post_before = null ): void { global $wp_rewrite; + // Only look up a real ID: get_post() falls back to the global post for an empty value. + if ( null === $post && (int) $post_id > 0 ) { + $post = get_post( (int) $post_id ); + } + if ( ! $post instanceof WP_Post ) { _doing_it_wrong( __FUNCTION__, @@ -1565,28 +1573,33 @@ function wp_random_content_flush_rewrite_rules_for_post( $post_id, $post, $updat return; } - if ( null !== $post_before && ! $post_before instanceof WP_Post ) { - _doing_it_wrong( - __FUNCTION__, - sprintf( - /* translators: 1: $post_before, 2: WP_Post */ - __( 'The %1$s argument must be a %2$s object or null.' ), - '$post_before', - 'WP_Post' - ), - '7.2.0' - ); - $post_before = null; + $posts = array( $post ); + + // Only wp_after_insert_post passes the previous post object, which matters when it was renamed or moved. + if ( 'wp_after_insert_post' === current_action() ) { + if ( $post_before instanceof WP_Post ) { + $posts[] = $post_before; + } elseif ( null !== $post_before ) { + _doing_it_wrong( + __FUNCTION__, + sprintf( + /* translators: 1: $post_before, 2: WP_Post */ + __( 'The %1$s argument must be a %2$s object or null.' ), + '$post_before', + 'WP_Post' + ), + '7.2.0' + ); + } } if ( ! $wp_rewrite->using_permalinks() || ! wp_is_random_content_redirect_enabled() ) { return; } - foreach ( array( $post, $post_before ) as $page ) { + foreach ( $posts as $page ) { if ( - $page instanceof WP_Post - && $wp_rewrite->get_random_base() === $page->post_name + $wp_rewrite->get_random_base() === $page->post_name && ( ( 'page' === $page->post_type && 0 === (int) $page->post_parent ) || ( 'post' === $page->post_type && $wp_rewrite->post_permalinks_share_random_base() ) diff --git a/tests/phpunit/tests/query/randomContentRedirect.php b/tests/phpunit/tests/query/randomContentRedirect.php index 71a98479484d7..4136c0263dbe1 100644 --- a/tests/phpunit/tests/query/randomContentRedirect.php +++ b/tests/phpunit/tests/query/randomContentRedirect.php @@ -232,6 +232,20 @@ protected function create_post( array $args = array() ): int { ); } + /** + * Creates a published post and returns the post object. + * + * @param array $args Optional. Post arguments. + * @return WP_Post Post object. + */ + protected function create_post_object( array $args = array() ): WP_Post { + $post = get_post( $this->create_post( $args ) ); + + $this->assertInstanceOf( WP_Post::class, $post ); + + return $post; + } + /** * Asserts that the request redirected to one of the given posts. * @@ -1237,15 +1251,23 @@ public function test_saving_unrelated_pages_does_not_flush_rewrite_rules(): void /** * @dataProvider data_invalid_arguments * - * @param array $args The arguments to call the function with. + * @param array $args The arguments to call the function with. */ public function test_invalid_arguments_do_not_flush_rewrite_rules( array $args ): void { + // The data provider cannot query the database, so PHP_INT_MAX stands in for a nonexistent post ID. + $args = array_map( + function ( $arg ) { + return PHP_INT_MAX === $arg ? $this->get_nonexistent_post_id() : $arg; + }, + $args + ); + $this->setExpectedIncorrectUsage( 'wp_random_content_flush_rewrite_rules_for_post' ); $flushes = new MockAction(); add_filter( 'pre_update_option_rewrite_rules', array( $flushes, 'filter' ) ); - wp_random_content_flush_rewrite_rules_for_post( ...$args ); + wp_random_content_flush_rewrite_rules_for_post( ...$args ); // @phpstan-ignore argument.type (Intentionally passing invalid values.) $this->assertSame( 0, $flushes->get_call_count(), 'Invalid arguments should not flush the rewrite rules.' ); } @@ -1257,25 +1279,114 @@ public function test_invalid_arguments_do_not_flush_rewrite_rules( array $args ) * arguments, but third-party code can. Doing so is incorrect usage and must not cause a * fatal error. * - * @return array + * `PHP_INT_MAX` is replaced with a post ID that is verified not to exist when the test runs. + * + * @return array}> */ public function data_invalid_arguments(): array { return array( - 'nulls' => array( array( null, null, false, null ) ), - 'strings' => array( array( 'random', 'random', 'yes', 'random' ) ), - 'arrays' => array( array( array(), array( 'post_name' => 'random' ), 1, array() ) ), - 'nulls without optional values' => array( array( null, null ) ), - 'strings without optional values' => array( array( 'random', 'random' ) ), + 'nulls' => array( array( null, null, false, null ) ), + 'strings' => array( array( 'random', 'random', 'yes', 'random' ) ), + 'arrays' => array( array( array(), array( 'post_name' => 'random' ), 1, array() ) ), + 'nulls without optional values' => array( array( null, null ) ), + 'strings without optional values' => array( array( 'random', 'random' ) ), + 'post ID of zero without a post' => array( array( 0 ) ), + 'nonexistent post ID without a post' => array( array( PHP_INT_MAX ) ), + ); + } + + /** + * Returns a post ID that does not exist. + * + * Starts at `PHP_INT_MAX` and decrements until no post is found. + * + * @return int Post ID. + */ + protected function get_nonexistent_post_id(): int { + $post_id = PHP_INT_MAX; + + while ( get_post( $post_id ) instanceof WP_Post ) { + --$post_id; + } + + return $post_id; + } + + /** + * Tests that an omitted post object does not fall back to the global post. + * + * `get_post()` returns the global post for an empty value, which would flush the + * rewrite rules for whatever post is being displayed rather than the one passed in. + */ + public function test_omitted_post_does_not_fall_back_to_the_global_post(): void { + $this->setExpectedIncorrectUsage( 'wp_random_content_flush_rewrite_rules_for_post' ); + + $GLOBALS['post'] = $this->create_post_object( + array( + 'post_type' => 'page', + 'post_name' => 'random', + ) + ); + + $flushes = new MockAction(); + add_filter( 'pre_update_option_rewrite_rules', array( $flushes, 'filter' ) ); + + wp_random_content_flush_rewrite_rules_for_post( null ); // @phpstan-ignore argument.type (Intentionally omitting the post ID.) + + $this->assertSame( 0, $flushes->get_call_count(), 'An omitted post should not be looked up from the global post.' ); + } + + /** + * Tests that an omitted post object is looked up from the post ID. + * + * The `$post` parameter was added to `after_delete_post` in 5.5.0, but third-party + * code written against the original signature may still fire the action with only + * the post ID. + */ + public function test_omitted_post_is_looked_up_from_the_post_id(): void { + $page_id = $this->create_post( + array( + 'post_type' => 'page', + 'post_name' => 'random', + ) ); + + $flushes = new MockAction(); + add_filter( 'pre_update_option_rewrite_rules', array( $flushes, 'filter' ) ); + + wp_random_content_flush_rewrite_rules_for_post( $page_id ); + + $this->assertSame( 1, $flushes->get_call_count(), 'A page at the random base should flush the rewrite rules when only its ID is passed.' ); + } + + /** + * Tests that firing `after_delete_post` with only a post ID does not cause a fatal error. + * + * As the post is already deleted, it cannot be looked up from the ID, so the call is + * incorrect usage and the rewrite rules are left alone. + * + * @covers ::_update_posts_count_on_delete + */ + public function test_after_delete_post_fired_with_only_a_post_id_does_not_flush_rewrite_rules(): void { + $post_id = $this->create_post(); + wp_delete_post( $post_id, true ); + + $this->setExpectedIncorrectUsage( 'wp_random_content_flush_rewrite_rules_for_post' ); + + $flushes = new MockAction(); + add_filter( 'pre_update_option_rewrite_rules', array( $flushes, 'filter' ) ); + + /** This action is documented in wp-includes/post.php */ + do_action( 'after_delete_post', $post_id ); // @phpstan-ignore wordpress.hookParamCountMismatch (Intentionally omitting the post object.) + + $this->assertSame( 0, $flushes->get_call_count(), 'Firing after_delete_post for a deleted post without the post object should not flush the rewrite rules.' ); } public function test_page_named_random_still_flushes_rewrite_rules_when_the_other_arguments_are_invalid(): void { - $page = get_post( - $this->create_post( - array( - 'post_type' => 'page', - 'post_name' => 'random', - ) + $page = $this->create_post_object( + array( + 'post_type' => 'page', + 'post_name' => 'random', ) ); @@ -1284,11 +1395,52 @@ public function test_page_named_random_still_flushes_rewrite_rules_when_the_othe $flushes = new MockAction(); add_filter( 'pre_update_option_rewrite_rules', array( $flushes, 'filter' ) ); - wp_random_content_flush_rewrite_rules_for_post( (string) $page->ID, $page, 'yes', false ); + do_action( 'wp_after_insert_post', (string) $page->ID, $page, 'yes', false ); $this->assertSame( 1, $flushes->get_call_count(), 'A page at the random base should flush the rewrite rules even when the other arguments are invalid.' ); } + /** + * Tests that the previous post object is only considered on `wp_after_insert_post`. + * + * `after_delete_post` has no previous post, so a value passed in its place is ignored + * rather than reported as incorrect usage. + */ + public function test_post_before_is_ignored_outside_wp_after_insert_post(): void { + $page_before = $this->create_post_object( + array( + 'post_type' => 'page', + 'post_name' => 'random', + ) + ); + $post = $this->create_post_object(); + + $flushes = new MockAction(); + add_filter( 'pre_update_option_rewrite_rules', array( $flushes, 'filter' ) ); + + wp_random_content_flush_rewrite_rules_for_post( $post->ID, $post, false, 'not a post' ); // @phpstan-ignore argument.type (Intentionally passing an invalid value.) + wp_random_content_flush_rewrite_rules_for_post( $post->ID, $post, false, $page_before ); + + $this->assertSame( 0, $flushes->get_call_count(), 'The previous post should not be considered outside wp_after_insert_post.' ); + } + + public function test_renamed_page_at_random_base_flushes_rewrite_rules_via_post_before(): void { + $page_before = $this->create_post_object( + array( + 'post_type' => 'page', + 'post_name' => 'random', + ) + ); + $post = $this->create_post_object(); + + $flushes = new MockAction(); + add_filter( 'pre_update_option_rewrite_rules', array( $flushes, 'filter' ) ); + + do_action( 'wp_after_insert_post', $post->ID, $post, true, $page_before ); + + $this->assertSame( 1, $flushes->get_call_count(), 'A page previously at the random base should flush the rewrite rules on wp_after_insert_post.' ); + } + /* * Opting out. */ From 91adb0786dbd0be9e1e565baebe38cc8c43ddbea Mon Sep 17 00:00:00 2001 From: Aaron Jorbin <622599+aaronjorbin@users.noreply.github.com> Date: Mon, 19 Oct 2026 16:26:26 -0500 Subject: [PATCH 4/5] clean up comments --- tests/phpunit/tests/query/randomContentRedirect.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/phpunit/tests/query/randomContentRedirect.php b/tests/phpunit/tests/query/randomContentRedirect.php index 4136c0263dbe1..398fad717d735 100644 --- a/tests/phpunit/tests/query/randomContentRedirect.php +++ b/tests/phpunit/tests/query/randomContentRedirect.php @@ -1376,8 +1376,7 @@ public function test_after_delete_post_fired_with_only_a_post_id_does_not_flush_ $flushes = new MockAction(); add_filter( 'pre_update_option_rewrite_rules', array( $flushes, 'filter' ) ); - /** This action is documented in wp-includes/post.php */ - do_action( 'after_delete_post', $post_id ); // @phpstan-ignore wordpress.hookParamCountMismatch (Intentionally omitting the post object.) + do_action( 'after_delete_post', $post_id ); // @phpstan-ignore WordPress.hookParamCountMismatch (Intentionally omitting the post object.) $this->assertSame( 0, $flushes->get_call_count(), 'Firing after_delete_post for a deleted post without the post object should not flush the rewrite rules.' ); } From e5bcd647c52fae9045581792241726c307353a97 Mon Sep 17 00:00:00 2001 From: Aaron Jorbin Date: Tue, 20 Oct 2026 09:55:59 -0500 Subject: [PATCH 5/5] Update src/wp-includes/ms-blogs.php Co-authored-by: tbradsha <32492176+tbradsha@users.noreply.github.com> --- src/wp-includes/ms-blogs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/ms-blogs.php b/src/wp-includes/ms-blogs.php index 117eaf6ef6fd6..d791dd2f4526d 100644 --- a/src/wp-includes/ms-blogs.php +++ b/src/wp-includes/ms-blogs.php @@ -895,7 +895,7 @@ function _update_blog_date_on_post_delete( $post_id ) { * @param WP_Post|null $post Optional. Post object. Default null. */ function _update_posts_count_on_delete( $post_id, $post = null ) { - if ( ! $post || 'publish' !== $post->post_status || 'post' !== $post->post_type ) { + if ( ! $post instanceof WP_Post || 'publish' !== $post->post_status || 'post' !== $post->post_type ) { return; }