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 c505df9b81644..c302e80157c2b 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-admin/options-privacy.php b/src/wp-admin/options-privacy.php index 4205967acb3a8..b4a784a43745e 100644 --- a/src/wp-admin/options-privacy.php +++ b/src/wp-admin/options-privacy.php @@ -127,20 +127,7 @@ static function ( $body_class ) { 'error' ); } else { - if ( 'trash' === $privacy_policy_page->post_status ) { - add_settings_error( - 'page_for_privacy_policy', - 'page_for_privacy_policy', - sprintf( - /* translators: %s: URL to Pages Trash. */ - __( 'The currently selected Privacy Policy page is in the Trash. Please create or select a new Privacy Policy page or restore the current page.' ), - 'edit.php?post_status=trash&post_type=page' - ), - 'error' - ); - } else { - $privacy_policy_page_exists = true; - } + $privacy_policy_page_exists = true; } } diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 4b6d9de25fa11..1a821bcae3400 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -577,7 +577,9 @@ 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( 'wp_trash_post', '_reset_privacy_policy_page_for_post' ); add_action( 'change_locale', 'create_initial_post_types' ); // Post Formats. diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index b225d35c48b2a..6ac7bafefdcc0 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -4027,6 +4027,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 deleted or trashed, 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 or trashed. + */ +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..1e4f0362327bd --- /dev/null +++ b/tests/phpunit/tests/privacy/wpPrivacyResetPolicyPageForPost.php @@ -0,0 +1,143 @@ +policy_page_id = self::factory()->post->create( array( 'post_type' => 'page' ) ); + update_option( 'wp_page_for_privacy_policy', $this->policy_page_id ); + } + + public function tear_down() { + delete_option( 'wp_page_for_privacy_policy' ); + parent::tear_down(); + } + + /** + * Tests that trashing the Privacy Policy page resets the option to 0. + * + * @ticket 56694 + */ + public function test_trashing_privacy_policy_page_resets_option() { + wp_trash_post( $this->policy_page_id ); + + $this->assertSame( 0, (int) get_option( '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() { + 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() { + $other_page_id = self::factory()->post->create( array( 'post_type' => 'page' ) ); + 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() { + $post_id = self::factory()->post->create( array( 'post_type' => 'post' ) ); + 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() { + 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' ) ); + wp_set_current_user( $user_id ); + wp_get_current_user()->add_cap( 'manage_privacy_options' ); + set_current_screen( 'post' ); + + $post = self::factory()->post->create_and_get( array( 'post_type' => 'page' ) ); + 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() { + 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' ) ); + wp_trash_post( $other_page_id ); + + $this->assertSame( + 0, + $call_count, + 'update_option() should not be called when wp_page_for_privacy_policy is already 0.' + ); + } +}