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;
}