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;
}
Comment on lines +332 to +336
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In practice, would this logic ever run now? Considering that the wp_page_for_privacy_policy option is set to 0 whenever the policy page is trashed or deleted, this condition would seem to just be here for extreme defensive programming. It doesn't seem necessary though.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@westonruter as far as i tested back then it was added so also existing websites where the privacy policy page was deleted it gets catched after the patch has been added. for websites deleting pages after the patch your right this will not be needed.


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
145 changes: 145 additions & 0 deletions tests/phpunit/tests/privacy/wpPrivacyResetPolicyPageForPost.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?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 );
if ( is_multisite() ) {
grant_super_admin( $user_id );
}
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