array(
'expiration' => __( 'Expiration' ),
'ip' => __( 'IP' ),
'ua' => __( 'User Agent' ),
'login' => __( 'Last Login' ),
);
foreach ( $session_tokens as $token_key => $session_token ) {
$session_tokens_data_to_export = array();
foreach ( $session_tokens_props_to_export as $key => $name ) {
if ( ! empty( $session_token[ $key ] ) ) {
$value = $session_token[ $key ];
if ( in_array( $key, array( 'expiration', 'login' ), true ) ) {
$value = date_i18n( 'F d, Y H:i A', $value );
}
$session_tokens_data_to_export[] = array(
'name' => $name,
'value' => $value,
);
}
}
$data_to_export[] = array(
'group_id' => 'session-tokens',
'group_label' => __( 'Session Tokens' ),
'group_description' => __( 'User’s Session Tokens data.' ),
'item_id' => "session-tokens-{$user->ID}-{$token_key}",
'data' => $session_tokens_data_to_export,
);
}
}
return array(
'data' => $data_to_export,
'done' => true,
);
}
/**
* Updates log when privacy request is confirmed.
*
* @since 4.9.6
* @access private
*
* @param int $request_id ID of the request.
*/
function _wp_privacy_account_request_confirmed( $request_id ) {
$request = wp_get_user_request( $request_id );
if ( ! $request ) {
return;
}
if ( ! in_array( $request->status, array( 'request-pending', 'request-failed' ), true ) ) {
return;
}
update_post_meta( $request_id, '_wp_user_request_confirmed_timestamp', time() );
wp_update_post(
array(
'ID' => $request_id,
'post_status' => 'request-confirmed',
)
);
}
/**
* Notifies the site administrator via email when a request is confirmed.
*
* Without this, the admin would have to manually check the site to see if any
* action was needed on their part yet.
*
* @since 4.9.6
*
* @param int $request_id The ID of the request.
*/
function _wp_privacy_send_request_confirmation_notification( $request_id ) {
$request = wp_get_user_request( $request_id );
if ( ! is_a( $request, 'WP_User_Request' ) || 'request-confirmed' !== $request->status ) {
return;
}
$already_notified = (bool) get_post_meta( $request_id, '_wp_admin_notified', true );
if ( $already_notified ) {
return;
}
if ( 'export_personal_data' === $request->action_name ) {
$manage_url = admin_url( 'export-personal-data.php' );
} elseif ( 'remove_personal_data' === $request->action_name ) {
$manage_url = admin_url( 'erase-personal-data.php' );
}
$action_description = wp_user_request_action_description( $request->action_name );
/**
* Filters the recipient of the data request confirmation notification.
*
* In a Multisite environment, this will default to the email address of the
* network admin because, by default, single site admins do not have the
* capabilities required to process requests. Some networks may wish to
* delegate those capabilities to a single-site admin, or a dedicated person
* responsible for managing privacy requests.
*
* @since 4.9.6
*
* @param string $admin_email The email address of the notification recipient.
* @param WP_User_Request $request The request that is initiating the notification.
*/
$admin_email = apply_filters( 'user_request_confirmed_email_to', get_site_option( 'admin_email' ), $request );
$email_data = array(
'request' => $request,
'user_email' => $request->email,
'description' => $action_description,
'manage_url' => $manage_url,
'sitename' => wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ),
'siteurl' => home_url(),
'admin_email' => $admin_email,
);
$subject = sprintf(
/* translators: Privacy data request confirmed notification email subject. 1: Site title, 2: Name of the confirmed action. */
__( '[%1$s] Action Confirmed: %2$s' ),
$email_data['sitename'],
$action_description
);
/**
* Filters the subject of the user request confirmation email.
*
* @since 4.9.8
*
* @param string $subject The email subject.
* @param string $sitename The name of the site.
* @param array $email_data {
* Data relating to the account action email.
*
* @type WP_User_Request $request User request object.
* @type string $user_email The email address confirming a request
* @type string $description Description of the action being performed so the user knows what the email is for.
* @type string $manage_url The link to click manage privacy requests of this type.
* @type string $sitename The site name sending the mail.
* @type string $siteurl The site URL sending the mail.
* @type string $admin_email The administrator email receiving the mail.
* }
*/
$subject = apply_filters( 'user_request_confirmed_email_subject', $subject, $email_data['sitename'], $email_data );
/* translators: Do not translate SITENAME, USER_EMAIL, DESCRIPTION, MANAGE_URL, SITEURL; those are placeholders. */
$content = __(
'Howdy,
A user data privacy request has been confirmed on ###SITENAME###:
User: ###USER_EMAIL###
Request: ###DESCRIPTION###
You can view and manage these data privacy requests here:
###MANAGE_URL###
Regards,
All at ###SITENAME###
###SITEURL###'
);
/**
* Filters the body of the user request confirmation email.
*
* The email is sent to an administrator when a user request is confirmed.
*
* The following strings have a special meaning and will get replaced dynamically:
*
* ###SITENAME### The name of the site.
* ###USER_EMAIL### The user email for the request.
* ###DESCRIPTION### Description of the action being performed so the user knows what the email is for.
* ###MANAGE_URL### The URL to manage requests.
* ###SITEURL### The URL to the site.
*
* @since 4.9.6
* @deprecated 5.8.0 Use {@see 'user_request_confirmed_email_content'} instead.
* For user erasure fulfillment email content
* use {@see 'user_erasure_fulfillment_email_content'} instead.
*
* @param string $content The email content.
* @param array $email_data {
* Data relating to the account action email.
*
* @type WP_User_Request $request User request object.
* @type string $user_email The email address confirming a request
* @type string $description Description of the action being performed
* so the user knows what the email is for.
* @type string $manage_url The link to click manage privacy requests of this type.
* @type string $sitename The site name sending the mail.
* @type string $siteurl The site URL sending the mail.
* @type string $admin_email The administrator email receiving the mail.
* }
*/
$content = apply_filters_deprecated(
'user_confirmed_action_email_content',
array( $content, $email_data ),
'5.8.0',
sprintf(
/* translators: 1 & 2: Deprecation replacement options. */
__( '%1$s or %2$s' ),
'user_request_confirmed_email_content',
'user_erasure_fulfillment_email_content'
)
);
/**
* Filters the body of the user request confirmation email.
*
* The email is sent to an administrator when a user request is confirmed.
* The following strings have a special meaning and will get replaced dynamically:
*
* ###SITENAME### The name of the site.
* ###USER_EMAIL### The user email for the request.
* ###DESCRIPTION### Description of the action being performed so the user knows what the email is for.
* ###MANAGE_URL### The URL to manage requests.
* ###SITEURL### The URL to the site.
*
* @since 5.8.0
*
* @param string $content The email content.
* @param array $email_data {
* Data relating to the account action email.
*
* @type WP_User_Request $request User request object.
* @type string $user_email The email address confirming a request
* @type string $description Description of the action being performed so the user knows what the email is for.
* @type string $manage_url The link to click manage privacy requests of this type.
* @type string $sitename The site name sending the mail.
* @type string $siteurl The site URL sending the mail.
* @type string $admin_email The administrator email receiving the mail.
* }
*/
$content = apply_filters( 'user_request_confirmed_email_content', $content, $email_data );
$content = str_replace( '###SITENAME###', $email_data['sitename'], $content );
$content = str_replace( '###USER_EMAIL###', $email_data['user_email'], $content );
$content = str_replace( '###DESCRIPTION###', $email_data['description'], $content );
$content = str_replace( '###MANAGE_URL###', sanitize_url( $email_data['manage_url'] ), $content );
$content = str_replace( '###SITEURL###', sanitize_url( $email_data['siteurl'] ), $content );
$headers = '';
/**
* Filters the headers of the user request confirmation email.
*
* @since 5.4.0
*
* @param string|array $headers The email headers.
* @param string $subject The email subject.
* @param string $content The email content.
* @param int $request_id The request ID.
* @param array $email_data {
* Data relating to the account action email.
*
* @type WP_User_Request $request User request object.
* @type string $user_email The email address confirming a request
* @type string $description Description of the action being performed so the user knows what the email is for.
* @type string $manage_url The link to click manage privacy requests of this type.
* @type string $sitename The site name sending the mail.
* @type string $siteurl The site URL sending the mail.
* @type string $admin_email The administrator email receiving the mail.
* }
*/
$headers = apply_filters( 'user_request_confirmed_email_headers', $headers, $subject, $content, $request_id, $email_data );
$email_sent = wp_mail( $email_data['admin_email'], $subject, $content, $headers );
if ( $email_sent ) {
update_post_meta( $request_id, '_wp_admin_notified', true );
}
}
/**
* Notifies the user when their erasure request is fulfilled.
*
* Without this, the user would never know if their data was actually erased.
*
* @since 4.9.6
*
* @param int $request_id The privacy request post ID associated with this request.
*/
function _wp_privacy_send_erasure_fulfillment_notification( $request_id ) {
$request = wp_get_user_request( $request_id );
if ( ! is_a( $request, 'WP_User_Request' ) || 'request-completed' !== $request->status ) {
return;
}
$already_notified = (bool) get_post_meta( $request_id, '_wp_user_notified', true );
if ( $already_notified ) {
return;
}
// Localize message content for user; fallback to site default for visitors.
if ( ! empty( $request->user_id ) ) {
$switched_locale = switch_to_user_locale( $request->user_id );
} else {
$switched_locale = switch_to_locale( get_locale() );
}
/**
* Filters the recipient of the data erasure fulfillment notification.
*
* @since 4.9.6
*
* @param string $user_email The email address of the notification recipient.
* @param WP_User_Request $request The request that is initiating the notification.
*/
$user_email = apply_filters( 'user_erasure_fulfillment_email_to', $request->email, $request );
$email_data = array(
'request' => $request,
'message_recipient' => $user_email,
'privacy_policy_url' => get_privacy_policy_url(),
'sitename' => wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ),
'siteurl' => home_url(),
);
$subject = sprintf(
/* translators: Erasure request fulfilled notification email subject. %s: Site title. */
__( '[%s] Erasure Request Fulfilled' ),
$email_data['sitename']
);
/**
* Filters the subject of the email sent when an erasure request is completed.
*
* @since 4.9.8
* @deprecated 5.8.0 Use {@see 'user_erasure_fulfillment_email_subject'} instead.
*
* @param string $subject The email subject.
* @param string $sitename The name of the site.
* @param array $email_data {
* Data relating to the account action email.
*
* @type WP_User_Request $request User request object.
* @type string $message_recipient The address that the email will be sent to. Defaults
* to the value of `$request->email`, but can be changed
* by the `user_erasure_fulfillment_email_to` filter.
* @type string $privacy_policy_url Privacy policy URL.
* @type string $sitename The site name sending the mail.
* @type string $siteurl The site URL sending the mail.
* }
*/
$subject = apply_filters_deprecated(
'user_erasure_complete_email_subject',
array( $subject, $email_data['sitename'], $email_data ),
'5.8.0',
'user_erasure_fulfillment_email_subject'
);
/**
* Filters the subject of the email sent when an erasure request is completed.
*
* @since 5.8.0
*
* @param string $subject The email subject.
* @param string $sitename The name of the site.
* @param array $email_data {
* Data relating to the account action email.
*
* @type WP_User_Request $request User request object.
* @type string $message_recipient The address that the email will be sent to. Defaults
* to the value of `$request->email`, but can be changed
* by the `user_erasure_fulfillment_email_to` filter.
* @type string $privacy_policy_url Privacy policy URL.
* @type string $sitename The site name sending the mail.
* @type string $siteurl The site URL sending the mail.
* }
*/
$subject = apply_filters( 'user_erasure_fulfillment_email_subject', $subject, $email_data['sitename'], $email_data );
/* translators: Do not translate SITENAME, SITEURL; those are placeholders. */
$content = __(
'Howdy,
Your request to erase your personal data on ###SITENAME### has been completed.
If you have any follow-up questions or concerns, please contact the site administrator.
Regards,
All at ###SITENAME###
###SITEURL###'
);
if ( ! empty( $email_data['privacy_policy_url'] ) ) {
/* translators: Do not translate SITENAME, SITEURL, PRIVACY_POLICY_URL; those are placeholders. */
$content = __(
'Howdy,
Your request to erase your personal data on ###SITENAME### has been completed.
If you have any follow-up questions or concerns, please contact the site administrator.
For more information, you can also read our privacy policy: ###PRIVACY_POLICY_URL###
Regards,
All at ###SITENAME###
###SITEURL###'
);
}
/**
* Filters the body of the data erasure fulfillment notification.
*
* The email is sent to a user when their data erasure request is fulfilled
* by an administrator.
*
* The following strings have a special meaning and will get replaced dynamically:
*
* ###SITENAME### The name of the site.
* ###PRIVACY_POLICY_URL### Privacy policy page URL.
* ###SITEURL### The URL to the site.
*
* @since 4.9.6
* @deprecated 5.8.0 Use {@see 'user_erasure_fulfillment_email_content'} instead.
* For user request confirmation email content
* use {@see 'user_request_confirmed_email_content'} instead.
*
* @param string $content The email content.
* @param array $email_data {
* Data relating to the account action email.
*
* @type WP_User_Request $request User request object.
* @type string $message_recipient The address that the email will be sent to. Defaults
* to the value of `$request->email`, but can be changed
* by the `user_erasure_fulfillment_email_to` filter.
* @type string $privacy_policy_url Privacy policy URL.
* @type string $sitename The site name sending the mail.
* @type string $siteurl The site URL sending the mail.
* }
*/
$content = apply_filters_deprecated(
'user_confirmed_action_email_content',
array( $content, $email_data ),
'5.8.0',
sprintf(
/* translators: 1 & 2: Deprecation replacement options. */
__( '%1$s or %2$s' ),
'user_erasure_fulfillment_email_content',
'user_request_confirmed_email_content'
)
);
/**
* Filters the body of the data erasure fulfillment notification.
*
* The email is sent to a user when their data erasure request is fulfilled
* by an administrator.
*
* The following strings have a special meaning and will get replaced dynamically:
*
* ###SITENAME### The name of the site.
* ###PRIVACY_POLICY_URL### Privacy policy page URL.
* ###SITEURL### The URL to the site.
*
* @since 5.8.0
*
* @param string $content The email content.
* @param array $email_data {
* Data relating to the account action email.
*
* @type WP_User_Request $request User request object.
* @type string $message_recipient The address that the email will be sent to. Defaults
* to the value of `$request->email`, but can be changed
* by the `user_erasure_fulfillment_email_to` filter.
* @type string $privacy_policy_url Privacy policy URL.
* @type string $sitename The site name sending the mail.
* @type string $siteurl The site URL sending the mail.
* }
*/
$content = apply_filters( 'user_erasure_fulfillment_email_content', $content, $email_data );
$content = str_replace( '###SITENAME###', $email_data['sitename'], $content );
$content = str_replace( '###PRIVACY_POLICY_URL###', $email_data['privacy_policy_url'], $content );
$content = str_replace( '###SITEURL###', sanitize_url( $email_data['siteurl'] ), $content );
$headers = '';
/**
* Filters the headers of the data erasure fulfillment notification.
*
* @since 5.4.0
* @deprecated 5.8.0 Use {@see 'user_erasure_fulfillment_email_headers'} instead.
*
* @param string|array $headers The email headers.
* @param string $subject The email subject.
* @param string $content The email content.
* @param int $request_id The request ID.
* @param array $email_data {
* Data relating to the account action email.
*
* @type WP_User_Request $request User request object.
* @type string $message_recipient The address that the email will be sent to. Defaults
* to the value of `$request->email`, but can be changed
* by the `user_erasure_fulfillment_email_to` filter.
* @type string $privacy_policy_url Privacy policy URL.
* @type string $sitename The site name sending the mail.
* @type string $siteurl The site URL sending the mail.
* }
*/
$headers = apply_filters_deprecated(
'user_erasure_complete_email_headers',
array( $headers, $subject, $content, $request_id, $email_data ),
'5.8.0',
'user_erasure_fulfillment_email_headers'
);
/**
* Filters the headers of the data erasure fulfillment notification.
*
* @since 5.8.0
*
* @param string|array $headers The email headers.
* @param string $subject The email subject.
* @param string $content The email content.
* @param int $request_id The request ID.
* @param array $email_data {
* Data relating to the account action email.
*
* @type WP_User_Request $request User request object.
* @type string $message_recipient The address that the email will be sent to. Defaults
* to the value of `$request->email`, but can be changed
* by the `user_erasure_fulfillment_email_to` filter.
* @type string $privacy_policy_url Privacy policy URL.
* @type string $sitename The site name sending the mail.
* @type string $siteurl The site URL sending the mail.
* }
*/
$headers = apply_filters( 'user_erasure_fulfillment_email_headers', $headers, $subject, $content, $request_id, $email_data );
$email_sent = wp_mail( $user_email, $subject, $content, $headers );
if ( $switched_locale ) {
restore_previous_locale();
}
if ( $email_sent ) {
update_post_meta( $request_id, '_wp_user_notified', true );
}
}
/**
* Returns request confirmation message HTML.
*
* @since 4.9.6
* @access private
*
* @param int $request_id The request ID being confirmed.
* @return string The confirmation message.
*/
function _wp_privacy_account_request_confirmed_message( $request_id ) {
$request = wp_get_user_request( $request_id );
$message = '' . __( 'Action has been confirmed.' ) . '
';
$message .= '' . __( 'The site administrator has been notified and will fulfill your request as soon as possible.' ) . '
';
if ( $request && in_array( $request->action_name, _wp_privacy_action_request_types(), true ) ) {
if ( 'export_personal_data' === $request->action_name ) {
$message = '' . __( 'Thanks for confirming your export request.' ) . '
';
$message .= '' . __( 'The site administrator has been notified. You will receive a link to download your export via email when they fulfill your request.' ) . '
';
} elseif ( 'remove_personal_data' === $request->action_name ) {
$message = '' . __( 'Thanks for confirming your erasure request.' ) . '
';
$message .= '' . __( 'The site administrator has been notified. You will receive an email confirmation when they erase your data.' ) . '
';
}
}
/**
* Filters the message displayed to a user when they confirm a data request.
*
* @since 4.9.6
*
* @param string $message The message to the user.
* @param int $request_id The ID of the request being confirmed.
*/
$message = apply_filters( 'user_request_action_confirmed_message', $message, $request_id );
return $message;
}
/**
* Creates and logs a user request to perform a specific action.
*
* Requests are stored inside a post type named `user_request` since they can apply to both
* users on the site, or guests without a user account.
*
* @since 4.9.6
* @since 5.7.0 Added the `$status` parameter.
*
* @param string $email_address User email address. This can be the address of a registered
* or non-registered user.
* @param string $action_name Name of the action that is being confirmed. Required.
* @param array $request_data Misc data you want to send with the verification request and pass
* to the actions once the request is confirmed.
* @param string $status Optional request status (pending or confirmed). Default 'pending'.
* @return int|WP_Error Returns the request ID if successful, or a WP_Error object on failure.
*/
function wp_create_user_request( $email_address = '', $action_name = '', $request_data = array(), $status = 'pending' ) {
$email_address = sanitize_email( $email_address );
$action_name = sanitize_key( $action_name );
if ( ! is_email( $email_address ) ) {
return new WP_Error( 'invalid_email', __( 'Invalid email address.' ) );
}
if ( ! in_array( $action_name, _wp_privacy_action_request_types(), true ) ) {
return new WP_Error( 'invalid_action', __( 'Invalid action name.' ) );
}
if ( ! in_array( $status, array( 'pending', 'confirmed' ), true ) ) {
return new WP_Error( 'invalid_status', __( 'Invalid request status.' ) );
}
$user = get_user_by( 'email', $email_address );
$user_id = $user && ! is_wp_error( $user ) ? $user->ID : 0;
// Check for duplicates.
$requests_query = new WP_Query(
array(
'post_type' => 'user_request',
'post_name__in' => array( $action_name ), // Action name stored in post_name column.
'title' => $email_address, // Email address stored in post_title column.
'post_status' => array(
'request-pending',
'request-confirmed',
),
'fields' => 'ids',
)
);
if ( $requests_query->found_posts ) {
return new WP_Error( 'duplicate_request', __( 'An incomplete personal data request for this email address already exists.' ) );
}
$request_id = wp_insert_post(
array(
'post_author' => $user_id,
'post_name' => $action_name,
'post_title' => $email_address,
'post_content' => wp_json_encode( $request_data ),
'post_status' => 'request-' . $status,
'post_type' => 'user_request',
'post_date' => current_time( 'mysql', false ),
'post_date_gmt' => current_time( 'mysql', true ),
),
true
);
return $request_id;
}
/**
* Gets action description from the name and return a string.
*
* @since 4.9.6
*
* @param string $action_name Action name of the request.
* @return string Human readable action name.
*/
function wp_user_request_action_description( $action_name ) {
switch ( $action_name ) {
case 'export_personal_data':
$description = __( 'Export Personal Data' );
break;
case 'remove_personal_data':
$description = __( 'Erase Personal Data' );
break;
default:
/* translators: %s: Action name. */
$description = sprintf( __( 'Confirm the "%s" action' ), $action_name );
break;
}
/**
* Filters the user action description.
*
* @since 4.9.6
*
* @param string $description The default description.
* @param string $action_name The name of the request.
*/
return apply_filters( 'user_request_action_description', $description, $action_name );
}
/**
* Send a confirmation request email to confirm an action.
*
* If the request is not already pending, it will be updated.
*
* @since 4.9.6
*
* @param string $request_id ID of the request created via wp_create_user_request().
* @return true|WP_Error True on success, `WP_Error` on failure.
*/
function wp_send_user_request( $request_id ) {
$request_id = absint( $request_id );
$request = wp_get_user_request( $request_id );
if ( ! $request ) {
return new WP_Error( 'invalid_request', __( 'Invalid personal data request.' ) );
}
// Localize message content for user; fallback to site default for visitors.
if ( ! empty( $request->user_id ) ) {
$switched_locale = switch_to_user_locale( $request->user_id );
} else {
$switched_locale = switch_to_locale( get_locale() );
}
$email_data = array(
'request' => $request,
'email' => $request->email,
'description' => wp_user_request_action_description( $request->action_name ),
'confirm_url' => add_query_arg(
array(
'action' => 'confirmaction',
'request_id' => $request_id,
'confirm_key' => wp_generate_user_request_key( $request_id ),
),
wp_login_url()
),
'sitename' => wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ),
'siteurl' => home_url(),
);
/* translators: Confirm privacy data request notification email subject. 1: Site title, 2: Name of the action. */
$subject = sprintf( __( '[%1$s] Confirm Action: %2$s' ), $email_data['sitename'], $email_data['description'] );
/**
* Filters the subject of the email sent when an account action is attempted.
*
* @since 4.9.6
*
* @param string $subject The email subject.
* @param string $sitename The name of the site.
* @param array $email_data {
* Data relating to the account action email.
*
* @type WP_User_Request $request User request object.
* @type string $email The email address this is being sent to.
* @type string $description Description of the action being performed so the user knows what the email is for.
* @type string $confirm_url The link to click on to confirm the account action.
* @type string $sitename The site name sending the mail.
* @type string $siteurl The site URL sending the mail.
* }
*/
$subject = apply_filters( 'user_request_action_email_subject', $subject, $email_data['sitename'], $email_data );
/* translators: Do not translate DESCRIPTION, CONFIRM_URL, SITENAME, SITEURL: those are placeholders. */
$content = __(
'Howdy,
A request has been made to perform the following action on your account:
###DESCRIPTION###
To confirm this, please click on the following link:
###CONFIRM_URL###
You can safely ignore and delete this email if you do not want to
take this action.
Regards,
All at ###SITENAME###
###SITEURL###'
);
/**
* Filters the text of the email sent when an account action is attempted.
*
* The following strings have a special meaning and will get replaced dynamically:
*
* ###DESCRIPTION### Description of the action being performed so the user knows what the email is for.
* ###CONFIRM_URL### The link to click on to confirm the account action.
* ###SITENAME### The name of the site.
* ###SITEURL### The URL to the site.
*
* @since 4.9.6
*
* @param string $content Text in the email.
* @param array $email_data {
* Data relating to the account action email.
*
* @type WP_User_Request $request User request object.
* @type string $email The email address this is being sent to.
* @type string $description Description of the action being performed so the user knows what the email is for.
* @type string $confirm_url The link to click on to confirm the account action.
* @type string $sitename The site name sending the mail.
* @type string $siteurl The site URL sending the mail.
* }
*/
$content = apply_filters( 'user_request_action_email_content', $content, $email_data );
$content = str_replace( '###DESCRIPTION###', $email_data['description'], $content );
$content = str_replace( '###CONFIRM_URL###', sanitize_url( $email_data['confirm_url'] ), $content );
$content = str_replace( '###EMAIL###', $email_data['email'], $content );
$content = str_replace( '###SITENAME###', $email_data['sitename'], $content );
$content = str_replace( '###SITEURL###', sanitize_url( $email_data['siteurl'] ), $content );
$headers = '';
/**
* Filters the headers of the email sent when an account action is attempted.
*
* @since 5.4.0
*
* @param string|array $headers The email headers.
* @param string $subject The email subject.
* @param string $content The email content.
* @param int $request_id The request ID.
* @param array $email_data {
* Data relating to the account action email.
*
* @type WP_User_Request $request User request object.
* @type string $email The email address this is being sent to.
* @type string $description Description of the action being performed so the user knows what the email is for.
* @type string $confirm_url The link to click on to confirm the account action.
* @type string $sitename The site name sending the mail.
* @type string $siteurl The site URL sending the mail.
* }
*/
$headers = apply_filters( 'user_request_action_email_headers', $headers, $subject, $content, $request_id, $email_data );
$email_sent = wp_mail( $email_data['email'], $subject, $content, $headers );
if ( $switched_locale ) {
restore_previous_locale();
}
if ( ! $email_sent ) {
return new WP_Error( 'privacy_email_error', __( 'Unable to send personal data export confirmation email.' ) );
}
return true;
}
/**
* Returns a confirmation key for a user action and stores the hashed version for future comparison.
*
* @since 4.9.6
*
* @param int $request_id Request ID.
* @return string Confirmation key.
*/
function wp_generate_user_request_key( $request_id ) {
global $wp_hasher;
// Generate something random for a confirmation key.
$key = wp_generate_password( 20, false );
// Return the key, hashed.
if ( empty( $wp_hasher ) ) {
require_once ABSPATH . WPINC . '/class-phpass.php';
$wp_hasher = new PasswordHash( 8, true );
}
wp_update_post(
array(
'ID' => $request_id,
'post_status' => 'request-pending',
'post_password' => $wp_hasher->HashPassword( $key ),
)
);
return $key;
}
/**
* Validates a user request by comparing the key with the request's key.
*
* @since 4.9.6
*
* @param string $request_id ID of the request being confirmed.
* @param string $key Provided key to validate.
* @return true|WP_Error True on success, WP_Error on failure.
*/
function wp_validate_user_request_key( $request_id, $key ) {
global $wp_hasher;
$request_id = absint( $request_id );
$request = wp_get_user_request( $request_id );
$saved_key = $request->confirm_key;
$key_request_time = $request->modified_timestamp;
if ( ! $request || ! $saved_key || ! $key_request_time ) {
return new WP_Error( 'invalid_request', __( 'Invalid personal data request.' ) );
}
if ( ! in_array( $request->status, array( 'request-pending', 'request-failed' ), true ) ) {
return new WP_Error( 'expired_request', __( 'This personal data request has expired.' ) );
}
if ( empty( $key ) ) {
return new WP_Error( 'missing_key', __( 'The confirmation key is missing from this personal data request.' ) );
}
if ( empty( $wp_hasher ) ) {
require_once ABSPATH . WPINC . '/class-phpass.php';
$wp_hasher = new PasswordHash( 8, true );
}
/**
* Filters the expiration time of confirm keys.
*
* @since 4.9.6
*
* @param int $expiration The expiration time in seconds.
*/
$expiration_duration = (int) apply_filters( 'user_request_key_expiration', DAY_IN_SECONDS );
$expiration_time = $key_request_time + $expiration_duration;
if ( ! $wp_hasher->CheckPassword( $key, $saved_key ) ) {
return new WP_Error( 'invalid_key', __( 'The confirmation key is invalid for this personal data request.' ) );
}
if ( ! $expiration_time || time() > $expiration_time ) {
return new WP_Error( 'expired_key', __( 'The confirmation key has expired for this personal data request.' ) );
}
return true;
}
/**
* Returns the user request object for the specified request ID.
*
* @since 4.9.6
*
* @param int $request_id The ID of the user request.
* @return WP_User_Request|false
*/
function wp_get_user_request( $request_id ) {
$request_id = absint( $request_id );
$post = get_post( $request_id );
if ( ! $post || 'user_request' !== $post->post_type ) {
return false;
}
return new WP_User_Request( $post );
}
/**
* Checks if Application Passwords is supported.
*
* Application Passwords is supported only by sites using SSL or local environments
* but may be made available using the {@see 'wp_is_application_passwords_available'} filter.
*
* @since 5.9.0
*
* @return bool
*/
function wp_is_application_passwords_supported() {
return is_ssl() || 'local' === wp_get_environment_type();
}
/**
* Checks if Application Passwords is globally available.
*
* By default, Application Passwords is available to all sites using SSL or to local environments.
* Use the {@see 'wp_is_application_passwords_available'} filter to adjust its availability.
*
* @since 5.6.0
*
* @return bool
*/
function wp_is_application_passwords_available() {
/**
* Filters whether Application Passwords is available.
*
* @since 5.6.0
*
* @param bool $available True if available, false otherwise.
*/
return apply_filters( 'wp_is_application_passwords_available', wp_is_application_passwords_supported() );
}
/**
* Checks if Application Passwords is available for a specific user.
*
* By default all users can use Application Passwords. Use {@see 'wp_is_application_passwords_available_for_user'}
* to restrict availability to certain users.
*
* @since 5.6.0
*
* @param int|WP_User $user The user to check.
* @return bool
*/
function wp_is_application_passwords_available_for_user( $user ) {
if ( ! wp_is_application_passwords_available() ) {
return false;
}
if ( ! is_object( $user ) ) {
$user = get_userdata( $user );
}
if ( ! $user || ! $user->exists() ) {
return false;
}
/**
* Filters whether Application Passwords is available for a specific user.
*
* @since 5.6.0
*
* @param bool $available True if available, false otherwise.
* @param WP_User $user The user to check.
*/
return apply_filters( 'wp_is_application_passwords_available_for_user', true, $user );
}
/**
* Registers the user meta property for persisted preferences.
*
* This property is used to store user preferences across page reloads and is
* currently used by the block editor for preferences like 'fullscreenMode' and
* 'fixedToolbar'.
*
* @since 6.1.0
* @access private
*
* @global wpdb $wpdb WordPress database abstraction object.
*/
function wp_register_persisted_preferences_meta() {
/*
* Create a meta key that incorporates the blog prefix so that each site
* on a multisite can have distinct user preferences.
*/
global $wpdb;
$meta_key = $wpdb->get_blog_prefix() . 'persisted_preferences';
register_meta(
'user',
$meta_key,
array(
'type' => 'object',
'single' => true,
'show_in_rest' => array(
'name' => 'persisted_preferences',
'type' => 'object',
'schema' => array(
'type' => 'object',
'context' => array( 'edit' ),
'properties' => array(
'_modified' => array(
'description' => __( 'The date and time the preferences were updated.' ),
'type' => 'string',
'format' => 'date-time',
'readonly' => false,
),
),
'additionalProperties' => true,
),
),
)
);
}