: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* WP_Privacy_Policy_Content class.
* @subpackage Administration
#[AllowDynamicProperties]
final class WP_Privacy_Policy_Content {
private static $policy_content = array();
private function __construct() {}
* Adds content to the postbox shown when editing the privacy policy.
* Plugins and themes should suggest text for inclusion in the site's privacy policy.
* The suggested text should contain information about any functionality that affects user privacy,
* and will be shown in the Suggested Privacy Policy Content postbox.
* Intended for use from `wp_add_privacy_policy_content()`.
* @param string $plugin_name The name of the plugin or theme that is suggesting content for the site's privacy policy.
* @param string $policy_text The suggested content for inclusion in the policy.
public static function add( $plugin_name, $policy_text ) {
if ( empty( $plugin_name ) || empty( $policy_text ) ) {
'plugin_name' => $plugin_name,
'policy_text' => $policy_text,
if ( ! in_array( $data, self::$policy_content, true ) ) {
self::$policy_content[] = $data;
* Performs a quick check to determine whether any privacy info has changed.
public static function text_change_check() {
$policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
// The site doesn't have a privacy policy.
if ( empty( $policy_page_id ) ) {
if ( ! current_user_can( 'edit_post', $policy_page_id ) ) {
$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.
$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
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 ( ! is_array( $data ) || ! empty( $data['removed'] ) ) {
'plugin_name' => $data['plugin_name'],
'policy_text' => $data['policy_text'],
// Normalize the order of texts, to facilitate comparison.
* The == operator (equal, not identical) was used intentionally.
* See https://www.php.net/manual/en/language.operators.array.php
* A plugin was activated or deactivated, or some policy text has changed.
* Show a notice on the relevant screens to inform the admin.
add_action( 'admin_notices', array( 'WP_Privacy_Policy_Content', 'policy_text_changed_notice' ) );
// 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;
* Outputs a warning when some privacy info has changed.
public static function policy_text_changed_notice() {
$screen = get_current_screen()->id;
if ( 'privacy' !== $screen ) {
$privacy_message = sprintf(
/* translators: %s: Privacy Policy Guide URL. */
__( 'The suggested privacy policy text has changed. Please <a href="%s">review the guide</a> and update your privacy policy.' ),
esc_url( admin_url( 'privacy-policy-guide.php?tab=policyguide' ) )
'additional_classes' => array( 'policy-text-updated' ),
* Updates the cached policy info when the policy page is updated.
* @param int $post_id The ID of the updated post.
public static function _policy_page_updated( $post_id ) {
$policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
if ( ! $policy_page_id || $policy_page_id !== (int) $post_id ) {
// Remove updated|removed status.
$old = (array) get_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );
foreach ( $old as $old_key => $old_data ) {
if ( ! empty( $old_data['removed'] ) ) {
// Remove the old policy text.
if ( ! empty( $old_data['updated'] ) ) {
// 'updated' is now 'added'.
'plugin_name' => $old_data['plugin_name'],
'policy_text' => $old_data['policy_text'],
'added' => $old_data['updated'],
delete_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );
foreach ( $done as $data ) {
add_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content', $data );
* Checks for updated, added or removed privacy policy information from plugins.
* Caches the current info in post_meta of the policy page.
* @return array The privacy policy text/information added by core and plugins.
public static function get_suggested_policy_text() {
$policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
$new = self::$policy_content;
$old = (array) get_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );
// Check for no-changes and updates.
foreach ( $new as $new_key => $new_data ) {
foreach ( $old as $old_key => $old_data ) {
if ( $new_data['policy_text'] === $old_data['policy_text'] ) {
// Use the new plugin name in case it was changed, translated, etc.
if ( $old_data['plugin_name'] !== $new_data['plugin_name'] ) {
$old_data['plugin_name'] = $new_data['plugin_name'];
// A plugin was re-activated.
if ( ! empty( $old_data['removed'] ) ) {
unset( $old_data['removed'] );
$old_data['added'] = $time;
} elseif ( $new_data['plugin_name'] === $old_data['plugin_name'] ) {
// The info for the policy was updated.
'plugin_name' => $new_data['plugin_name'],
'policy_text' => $new_data['policy_text'],
unset( $new[ $new_key ], $old[ $old_key ] );
// A plugin was activated.
foreach ( $new as $new_data ) {
if ( ! empty( $new_data['plugin_name'] ) && ! empty( $new_data['policy_text'] ) ) {
$new_data['added'] = $time;
// A plugin was deactivated.
foreach ( $old as $old_data ) {
if ( ! empty( $old_data['plugin_name'] ) && ! empty( $old_data['policy_text'] ) ) {
'plugin_name' => $old_data['plugin_name'],
'policy_text' => $old_data['policy_text'],
if ( $update_cache && $policy_page_id ) {
delete_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content' );
foreach ( $checked as $data ) {
add_post_meta( $policy_page_id, '_wp_suggested_privacy_policy_content', $data );
* Adds a notice with a link to the guide when editing the privacy policy page.
* @since 5.0.0 The `$post` parameter was made optional.
* @global WP_Post $post Global post object.
* @param WP_Post|null $post The currently edited post. Default null.
public static function notice( $post = null ) {
if ( is_null( $post ) ) {
$post = get_post( $post );
if ( ! ( $post instanceof WP_Post ) ) {
if ( ! current_user_can( 'manage_privacy_options' ) ) {
$current_screen = get_current_screen();
$policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
if ( 'post' !== $current_screen->base || $policy_page_id !== $post->ID ) {
$message = __( 'Need help putting together your new Privacy Policy page? Check out our guide for recommendations on what content to include, along with policies suggested by your plugins and theme.' );
$url = esc_url( admin_url( 'options-privacy.php?tab=policyguide' ) );
$label = __( 'View Privacy Policy Guide.' );
if ( get_current_screen()->is_block_editor() ) {
wp_enqueue_script( 'wp-notices' );
'wp.data.dispatch( "core/notices" ).createWarningNotice( "%s", { actions: [ %s ], isDismissible: false } )',
wp_json_encode( $action )
' <a href="%s" target="_blank">%s <span class="screen-reader-text">%s</span></a>',
/* translators: Hidden accessibility text. */
__( '(opens in a new tab)' )
'additional_classes' => array( 'inline', 'wp-pp-notice' ),
* Outputs the privacy policy guide together with content from the theme and plugins.
public static function privacy_policy_guide() {
$content_array = self::get_suggested_policy_text();
$date_format = __( 'F j, Y' );
foreach ( $content_array as $section ) {
if ( ! empty( $section['removed'] ) ) {
$date = date_i18n( $date_format, $section['removed'] );
/* translators: %s: Date of plugin deactivation. */
$badge_title = sprintf( __( 'Removed %s.' ), $date );
/* translators: %s: Date of plugin deactivation. */
$removed = sprintf( __( 'You deactivated this plugin on %s and may no longer need this policy.' ), $date );
$removed = wp_get_admin_notice(
'additional_classes' => array( 'inline' ),
} elseif ( ! empty( $section['updated'] ) ) {
$date = date_i18n( $date_format, $section['updated'] );
/* translators: %s: Date of privacy policy text update. */
$badge_title = sprintf( __( 'Updated %s.' ), $date );
$plugin_name = esc_html( $section['plugin_name'] );
$sanitized_policy_name = sanitize_title_with_dashes( $plugin_name );
<h4 class="privacy-settings-accordion-heading">
<button aria-expanded="false" class="privacy-settings-accordion-trigger" aria-controls="privacy-settings-accordion-block-<?php echo $sanitized_policy_name; ?>" type="button">
<span class="title"><?php echo $plugin_name; ?></span>
<?php if ( ! empty( $section['removed'] ) || ! empty( $section['updated'] ) ) : ?>
<span class="badge <?php echo $badge_class; ?>"> <?php echo $badge_title; ?></span>
<span class="icon"></span>
<div id="privacy-settings-accordion-block-<?php echo $sanitized_policy_name; ?>" class="privacy-settings-accordion-panel privacy-text-box-body" hidden="hidden">
echo $section['policy_text'];
<?php if ( empty( $section['removed'] ) ) : ?>
<div class="privacy-settings-accordion-actions">
<span class="success" aria-hidden="true"><?php _e( 'Copied!' ); ?></span>
<button type="button" class="privacy-text-copy button">
<span aria-hidden="true"><?php _e( 'Copy suggested policy text to clipboard' ); ?></span>
<span class="screen-reader-text">
/* translators: Hidden accessibility text. %s: Plugin name. */
printf( __( 'Copy suggested policy text from %s.' ), $plugin_name );
* Returns the default suggested privacy policy content.
* @since 5.0.0 Added the `$blocks` parameter.
* @param bool $description Whether to include the descriptions under the section headings. Default false.
* @param bool $blocks Whether to format the content for the block editor. Default true.
* @return string The default policy content.
public static function get_default_content( $description = false, $blocks = true ) {
$suggested_text = '<strong class="privacy-policy-tutorial">' . __( 'Suggested text:' ) . ' </strong>';
// Start of the suggested privacy policy text.
$strings[] = '<div class="wp-suggested-text">';
/* translators: Default privacy policy heading. */
$strings[] = '<h2 class="wp-block-heading">' . __( 'Who we are' ) . '</h2>';
/* translators: Privacy policy tutorial. */
$strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should note your site URL, as well as the name of the company, organization, or individual behind it, and some accurate contact information.' ) . '</p>';
/* translators: Privacy policy tutorial. */
$strings[] = '<p class="privacy-policy-tutorial">' . __( 'The amount of information you may be required to show will vary depending on your local or national business regulations. You may, for example, be required to display a physical address, a registered address, or your company registration number.' ) . '</p>';
/* translators: Default privacy policy text. %s: Site URL. */
$strings[] = '<p>' . $suggested_text . sprintf( __( 'Our website address is: %s.' ), get_bloginfo( 'url', 'display' ) ) . '</p>';
/* translators: Default privacy policy heading. */
$strings[] = '<h2>' . __( 'What personal data we collect and why we collect it' ) . '</h2>';
/* translators: Privacy policy tutorial. */
$strings[] = '<p class="privacy-policy-tutorial">' . __( 'In this section you should note what personal data you collect from users and site visitors. This may include personal data, such as name, email address, personal account preferences; transactional data, such as purchase information; and technical data, such as information about cookies.' ) . '</p>';
/* translators: Privacy policy tutorial. */
$strings[] = '<p class="privacy-policy-tutorial">' . __( 'You should also note any collection and retention of sensitive personal data, such as data concerning health.' ) . '</p>';
/* translators: Privacy policy tutorial. */
$strings[] = '<p class="privacy-policy-tutorial">' . __( 'In addition to listing what personal data you collect, you need to note why you collect it. These explanations must note either the legal basis for your data collection and retention or the active consent the user has given.' ) . '</p>';
/* translators: Privacy policy tutorial. */
$strings[] = '<p class="privacy-policy-tutorial">' . __( 'Personal data is not just created by a user’s interactions with your site. Personal data is also generated from technical processes such as contact forms, comments, cookies, analytics, and third party embeds.' ) . '</p>';
/* translators: Privacy policy tutorial. */
$strings[] = '<p class="privacy-policy-tutorial">' . __( 'By default WordPress does not collect any personal data about visitors, and only collects the data shown on the User Profile screen from registered users. However some of your plugins may collect personal data. You should add the relevant information below.' ) . '</p>';
/* translators: Default privacy policy heading. */
$strings[] = '<h2 class="wp-block-heading">' . __( 'Comments' ) . '</h2>';
/* translators: Privacy policy tutorial. */