Skip to content
6 changes: 6 additions & 0 deletions src/wp-admin/includes/class-wp-privacy-policy-content.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
15 changes: 1 addition & 14 deletions src/wp-admin/options-privacy.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="%s">restore the current page</a>.' ),
'edit.php?post_status=trash&post_type=page'
),
'error'
);
} else {
$privacy_policy_page_exists = true;
}
$privacy_policy_page_exists = true;
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/wp-includes/default-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
16 changes: 16 additions & 0 deletions src/wp-includes/post.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
143 changes: 143 additions & 0 deletions tests/phpunit/tests/privacy/wpPrivacyResetPolicyPageForPost.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php
/**
* Tests for _reset_privacy_policy_page_for_post() and the self-healing
* check in WP_Privacy_Policy_Content::notice().
*
* @package WordPress
* @subpackage UnitTests
* @since 7.1.0
*
* @group privacy
*
* @covers ::_reset_privacy_policy_page_for_post
*/
class Tests_Privacy_WpPrivacyResetPolicyPageForPost extends WP_UnitTestCase {
/**
* ID of the page set as the Privacy Policy page.
*
* @var int
*/
private $policy_page_id;

public function set_up() {
parent::set_up();

$this->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.'
);
}
}
Loading