diff --git a/src/wp-admin/css/forms.css b/src/wp-admin/css/forms.css index 2e47430f06..164878ac7e 100644 --- a/src/wp-admin/css/forms.css +++ b/src/wp-admin/css/forms.css @@ -1089,7 +1089,7 @@ table.form-table td .updated p { } .tools-privacy-edit { - margin: 2.3em 0; + margin: 1.5em 0; } .tools-privacy-policy-page span { diff --git a/src/wp-admin/includes/admin-filters.php b/src/wp-admin/includes/admin-filters.php index 68592b6d41..233f40f6dd 100644 --- a/src/wp-admin/includes/admin-filters.php +++ b/src/wp-admin/includes/admin-filters.php @@ -138,7 +138,7 @@ add_filter( 'wp_privacy_personal_data_export_page', 'wp_privacy_process_personal add_action( 'wp_privacy_personal_data_export_file', 'wp_privacy_generate_personal_data_export_file', 10 ); // Privacy policy text changes check. -add_action( 'admin_init', array( 'WP_Privacy_Policy_Content', 'text_change_check' ), 20 ); +add_action( 'admin_init', array( 'WP_Privacy_Policy_Content', 'text_change_check' ), 100 ); // Show a "postbox" with the text suggestions for a privacy policy. add_action( 'edit_form_after_title', array( 'WP_Privacy_Policy_Content', 'notice' ) ); @@ -146,5 +146,5 @@ add_action( 'edit_form_after_title', array( 'WP_Privacy_Policy_Content', 'notice // Add the suggested policy text from WordPress. add_action( 'admin_init', array( 'WP_Privacy_Policy_Content', 'add_suggested_content' ), 1 ); -// Stop checking for text changes after the policy page is updated. +// Update the cached policy info when the policy page is updated. add_action( 'post_updated', array( 'WP_Privacy_Policy_Content', '_policy_page_updated' ) ); diff --git a/src/wp-admin/includes/misc.php b/src/wp-admin/includes/misc.php index ff8e78a98e..e9d7641595 100644 --- a/src/wp-admin/includes/misc.php +++ b/src/wp-admin/includes/misc.php @@ -1320,69 +1320,94 @@ final class WP_Privacy_Policy_Content { // The site doesn't have a privacy policy. if ( empty( $policy_page_id ) ) { - return; + return false; } if ( ! current_user_can( 'edit_post', $policy_page_id ) ) { - return; - } - - // Also run when the option doesn't exist yet. - if ( get_option( '_wp_privacy_text_change_check' ) === 'no-check' ) { - return; + return false; } $old = (array) get_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' ); + + // Updates are not relevant if the user has not reviewed any suggestions yet. + if ( empty( $old ) ) { + return false; + } + + $cached = get_option( '_wp_suggested_policy_text_has_changed' ); + + /* + * When this function is called before `admin_init`, `self::$policy_content` + * has not been populated yet, so use the cached result from the last + * execution instead. + */ + if ( ! did_action( 'admin_init' ) ) { + return 'changed' === $cached; + } + $new = self::$policy_content; // Remove the extra values added to the meta. foreach ( $old as $key => $data ) { + if ( ! empty( $data['removed'] ) ) { + unset( $old[ $key ] ); + continue; + } + $old[ $key ] = array( 'plugin_name' => $data['plugin_name'], 'policy_text' => $data['policy_text'], ); } + // Normalize the order of texts, to facilitate comparison. + sort( $old ); + sort( $new ); + // The == operator (equal, not identical) was used intentionally. // See http://php.net/manual/en/language.operators.array.php if ( $new != $old ) { // A plugin was activated or deactivated, or some policy text has changed. - // Show a notice on all screens in wp-admin. + // Show a notice on the relevant screens to inform the admin. add_action( 'admin_notices', array( 'WP_Privacy_Policy_Content', 'policy_text_changed_notice' ) ); + $state = 'changed'; } else { - // Stop checking. - update_option( '_wp_privacy_text_change_check', 'no-check' ); + $state = 'not-changed'; } + + // Cache the result for use before `admin_init` (see above). + if ( $cached !== $state ) { + update_option( '_wp_suggested_policy_text_has_changed', $state ); + } + + return 'changed' === $state; } /** - * Output an admin notice when some privacy info has changed. + * Output a warning when some privacy info has changed. * * @since 4.9.6 */ public static function policy_text_changed_notice() { global $post; - $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' ); + + $screen = get_current_screen()->id; + + if ( 'privacy' !== $screen ) { + return; + } ?>

ID != $policy_page_id ) { - ?> - -

$old_data['policy_text'], 'removed' => $time, ); - array_unshift( $checked, $data ); + + $checked[] = $data; } } $update_cache = true; @@ -1525,11 +1550,6 @@ final class WP_Privacy_Policy_Content { } } - // Stop checking for changes after the page has been loaded. - if ( get_option( '_wp_privacy_text_change_check' ) !== 'no-check' ) { - update_option( '_wp_privacy_text_change_check', 'no-check' ); - } - return $checked; } diff --git a/src/wp-admin/menu.php b/src/wp-admin/menu.php index 0ad302ebda..bf9eb72abb 100644 --- a/src/wp-admin/menu.php +++ b/src/wp-admin/menu.php @@ -263,14 +263,21 @@ if ( ! is_multisite() && defined( 'WP_ALLOW_MULTISITE' ) && WP_ALLOW_MULTISITE ) $submenu['tools.php'][50] = array( __( 'Network Setup' ), 'setup_network', 'network.php' ); } -$menu[80] = array( __( 'Settings' ), 'manage_options', 'options-general.php', '', 'menu-top menu-icon-settings', 'menu-settings', 'dashicons-admin-settings' ); +$change_notice = ''; +if ( current_user_can( 'manage_privacy_options' ) && WP_Privacy_Policy_Content::text_change_check() ) { + $change_notice = ' ' . number_format_i18n( 1 ) . ''; +} + +// translators: %s is the update notification bubble, if updates are available. +$menu[80] = array( sprintf( __( 'Settings %s' ), $change_notice ), 'manage_options', 'options-general.php', '', 'menu-top menu-icon-settings', 'menu-settings', 'dashicons-admin-settings' ); $submenu['options-general.php'][10] = array( _x( 'General', 'settings screen' ), 'manage_options', 'options-general.php' ); $submenu['options-general.php'][15] = array( __( 'Writing' ), 'manage_options', 'options-writing.php' ); $submenu['options-general.php'][20] = array( __( 'Reading' ), 'manage_options', 'options-reading.php' ); $submenu['options-general.php'][25] = array( __( 'Discussion' ), 'manage_options', 'options-discussion.php' ); $submenu['options-general.php'][30] = array( __( 'Media' ), 'manage_options', 'options-media.php' ); $submenu['options-general.php'][40] = array( __( 'Permalinks' ), 'manage_options', 'options-permalink.php' ); - $submenu['options-general.php'][45] = array( __( 'Privacy' ), 'manage_privacy_options', 'privacy.php' ); + // translators: %s is the update notification bubble, if updates are available. + $submenu['options-general.php'][45] = array( sprintf( __( 'Privacy %s' ), $change_notice ), 'manage_privacy_options', 'privacy.php' ); $_wp_last_utility_menu = 80; // The index of the last top-level menu in the utility menu group diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 7ecff3ade1..61c45e6231 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -562,8 +562,4 @@ add_filter( 'pre_oembed_result', 'wp_filter_pre_oembed_result', 10, 3 ); // Capabilities add_filter( 'user_has_cap', 'wp_maybe_grant_install_languages_cap', 1 ); -// Trigger the check for policy text changes after active plugins change. -add_action( 'update_site_option_active_sitewide_plugins', '_wp_privacy_active_plugins_change' ); -add_action( 'update_option_active_plugins', '_wp_privacy_active_plugins_change' ); - unset( $filter, $action ); diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 341a1746d9..763b98df28 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -6248,16 +6248,6 @@ function wp_privacy_anonymize_data( $type, $data = '' ) { return apply_filters( 'wp_privacy_anonymize_data', $anonymous, $type, $data ); } -/** - * Trigger the check for policy text changes. - * - * @since 4.9.6 - * @access private - */ -function _wp_privacy_active_plugins_change() { - update_option( '_wp_privacy_text_change_check', 'check' ); -} - /** * Schedule a `WP_Cron` job to delete expired export files. *