diff --git a/src/wp-admin/includes/class-wp-privacy-policy-content.php b/src/wp-admin/includes/class-wp-privacy-policy-content.php index 2f7ec2108d22f..141f073cd260c 100644 --- a/src/wp-admin/includes/class-wp-privacy-policy-content.php +++ b/src/wp-admin/includes/class-wp-privacy-policy-content.php @@ -329,6 +329,12 @@ public static function notice( $post = null ) { $current_screen = get_current_screen(); $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' ); + // If the privacy policy page has been deleted, reset the option and bail. + if ( $policy_page_id && ! get_post( $policy_page_id ) ) { + update_option( 'wp_page_for_privacy_policy', 0 ); + return; + } + if ( 'post' !== $current_screen->base || $policy_page_id !== $post->ID ) { return; } diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index b2790c7d43ec9..66504d37ad84d 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -590,6 +590,7 @@ add_action( 'init', 'create_initial_post_types', 0 ); // Highest priority. add_action( 'admin_menu', '_add_post_type_submenus' ); add_action( 'before_delete_post', '_reset_front_page_settings_for_post' ); +add_action( 'before_delete_post', '_reset_privacy_policy_page_for_post' ); add_action( 'wp_trash_post', '_reset_front_page_settings_for_post' ); add_action( 'change_locale', 'create_initial_post_types' ); diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 3813176140bb4..da3abfbd7d61c 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -4052,6 +4052,22 @@ function _reset_front_page_settings_for_post( $post_id ) { unstick_post( $post->ID ); } +/** + * Resets the Privacy Policy page ID option when the Privacy Policy page + * is permanently deleted, to prevent uncached database queries for a + * non-existent page. + * + * @since 7.1.0 + * @access private + * + * @param int $post_id The ID of the post being deleted. + */ +function _reset_privacy_policy_page_for_post( int $post_id ): void { + if ( 'page' === get_post_type( $post_id ) && ( (int) get_option( 'wp_page_for_privacy_policy' ) === $post_id ) ) { + update_option( 'wp_page_for_privacy_policy', 0 ); + } +} + /** * Moves a post or page to the Trash * diff --git a/tests/phpunit/tests/privacy/wpPrivacyResetPolicyPageForPost.php b/tests/phpunit/tests/privacy/wpPrivacyResetPolicyPageForPost.php new file mode 100644 index 0000000000000..50ce04cb1bd44 --- /dev/null +++ b/tests/phpunit/tests/privacy/wpPrivacyResetPolicyPageForPost.php @@ -0,0 +1,172 @@ +post->create( array( 'post_type' => 'page' ) ); + assert( is_int( $page_id ) ); + $this->policy_page_id = $page_id; + update_option( 'wp_page_for_privacy_policy', $this->policy_page_id ); + } + + public function tear_down(): void { + delete_option( 'wp_page_for_privacy_policy' ); + parent::tear_down(); + } + + /** + * Tests that trashing the Privacy Policy page does NOT reset the option, + * so that restoring from trash preserves the assignment. + * + * @ticket 56694 + */ + public function test_trashing_privacy_policy_page_does_not_reset_option(): void { + wp_trash_post( $this->policy_page_id ); + + $this->assertSame( + $this->policy_page_id, + (int) get_option( 'wp_page_for_privacy_policy' ), + 'Trashing the Privacy Policy page should not reset wp_page_for_privacy_policy.' + ); + } + + /** + * Tests that permanently deleting the Privacy Policy page resets the option to 0. + * + * @ticket 56694 + */ + public function test_deleting_privacy_policy_page_resets_option(): void { + wp_delete_post( $this->policy_page_id, true ); + + $this->assertSame( 0, (int) get_option( 'wp_page_for_privacy_policy' ) ); + } + + /** + * Tests that trashing a different page does not change the option. + * + * @ticket 56694 + */ + public function test_trashing_a_different_page_does_not_reset_option(): void { + $other_page_id = self::factory()->post->create( array( 'post_type' => 'page' ) ); + $this->assertIsInt( $other_page_id ); + wp_trash_post( $other_page_id ); + + $this->assertSame( + $this->policy_page_id, + (int) get_option( 'wp_page_for_privacy_policy' ), + 'Trashing an unrelated page should not reset wp_page_for_privacy_policy.' + ); + } + + /** + * Tests that deleting a non-page post type does not change the option. + * + * @ticket 56694 + */ + public function test_deleting_non_page_post_type_does_not_reset_option(): void { + $post_id = self::factory()->post->create( array( 'post_type' => 'post' ) ); + $this->assertIsInt( $post_id ); + wp_delete_post( $post_id, true ); + + $this->assertSame( + $this->policy_page_id, + (int) get_option( 'wp_page_for_privacy_policy' ), + 'Deleting a non-page post should not reset wp_page_for_privacy_policy.' + ); + } + + /** + * Tests that WP_Privacy_Policy_Content::notice() resets the option to 0 + * when the stored ID points to a page that no longer exists. + * + * @ticket 56694 + * + * @covers WP_Privacy_Policy_Content::notice + */ + public function test_notice_self_heals_when_policy_page_does_not_exist(): void { + require_once ABSPATH . 'wp-admin/includes/class-wp-privacy-policy-content.php'; + + update_option( 'wp_page_for_privacy_policy', 99999 ); + + $user_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); + $this->assertIsInt( $user_id ); + wp_set_current_user( $user_id ); + if ( is_multisite() ) { + grant_super_admin( $user_id ); + } + set_current_screen( 'post' ); + + $post = self::factory()->post->create_and_get( array( 'post_type' => 'page' ) ); + $this->assertInstanceOf( WP_Post::class, $post ); + WP_Privacy_Policy_Content::notice( $post ); + + $this->assertSame( + 0, + (int) get_option( 'wp_page_for_privacy_policy' ), + 'notice() should reset the option to 0 when the stored page does not exist.' + ); + } + + /** + * Tests that _reset_privacy_policy_page_for_post() does not call + * update_option() when wp_page_for_privacy_policy is already 0. + * + * @ticket 56694 + */ + public function test_no_update_option_when_policy_page_already_zero(): void { + update_option( 'wp_page_for_privacy_policy', 0 ); + + $call_count = 0; + add_filter( + 'pre_update_option_wp_page_for_privacy_policy', + static function ( $value ) use ( &$call_count ) { + ++$call_count; + return $value; + } + ); + + $other_page_id = self::factory()->post->create( array( 'post_type' => 'page' ) ); + $this->assertIsInt( $other_page_id ); + wp_delete_post( $other_page_id, true ); + + $this->assertSame( + 0, + $call_count, + 'update_option() should not be called when wp_page_for_privacy_policy is already 0.' + ); + } + + /** + * Tests that untrashing the Privacy Policy page preserves the option, + * confirming the trash/restore cycle keeps the assignment intact. + * + * @ticket 56694 + */ + public function test_untrashing_privacy_policy_page_preserves_option(): void { + wp_trash_post( $this->policy_page_id ); + wp_untrash_post( $this->policy_page_id ); + + $this->assertSame( + $this->policy_page_id, + (int) get_option( 'wp_page_for_privacy_policy' ), + 'Untrashing the Privacy Policy page should preserve wp_page_for_privacy_policy.' + ); + } +}