From aba945444eff7308957b642d17e40bda7ff72ae8 Mon Sep 17 00:00:00 2001
From: Paul Biron <paul@sparrowhawkcomputing.com>
Date: Thu, 2 Apr 2020 13:22:18 -0600
Subject: [PATCH] Add UI for managing requests at the network-level.
---
src/wp-admin/erase-personal-data.php | 2 +-
src/wp-admin/export-personal-data.php | 2 +-
.../class-wp-privacy-requests-table.php | 22 ++++++++
src/wp-admin/includes/privacy-tools.php | 53 ++++++++++++++-----
src/wp-admin/network/erase-personal-data.php | 12 +++++
src/wp-admin/network/export-personal-data.php | 12 +++++
src/wp-admin/network/menu.php | 5 ++
src/wp-includes/admin-bar.php | 11 ++++
src/wp-includes/class-wp-user-request.php | 14 +++++
src/wp-includes/user.php | 46 ++++++++++++----
10 files changed, 153 insertions(+), 26 deletions(-)
create mode 100644 src/wp-admin/network/erase-personal-data.php
create mode 100644 src/wp-admin/network/export-personal-data.php
diff --git a/src/wp-admin/erase-personal-data.php b/src/wp-admin/erase-personal-data.php
index d674633f0f..08601d9b7f 100644
|
a
|
b
|
require_once ABSPATH . 'wp-admin/admin-header.php'; |
| 56 | 56 | |
| 57 | 57 | <?php settings_errors(); ?> |
| 58 | 58 | |
| 59 | | <form action="<?php echo esc_url( admin_url( 'erase-personal-data.php' ) ); ?>" method="post" class="wp-privacy-request-form"> |
| | 59 | <form action="<?php echo esc_url( self_admin_url( 'erase-personal-data.php' ) ); ?>" method="post" class="wp-privacy-request-form"> |
| 60 | 60 | <h2><?php esc_html_e( 'Add Data Erasure Request' ); ?></h2> |
| 61 | 61 | <p><?php esc_html_e( 'An email will be sent to the user at this email address asking them to verify the request.' ); ?></p> |
| 62 | 62 | |
diff --git a/src/wp-admin/export-personal-data.php b/src/wp-admin/export-personal-data.php
index 8e44345a99..bea49bccf8 100644
|
a
|
b
|
require_once ABSPATH . 'wp-admin/admin-header.php'; |
| 56 | 56 | |
| 57 | 57 | <?php settings_errors(); ?> |
| 58 | 58 | |
| 59 | | <form action="<?php echo esc_url( admin_url( 'export-personal-data.php' ) ); ?>" method="post" class="wp-privacy-request-form"> |
| | 59 | <form action="<?php echo esc_url( self_admin_url( 'export-personal-data.php' ) ); ?>" method="post" class="wp-privacy-request-form"> |
| 60 | 60 | <h2><?php esc_html_e( 'Add Data Export Request' ); ?></h2> |
| 61 | 61 | <p><?php esc_html_e( 'An email will be sent to the user at this email address asking them to verify the request.' ); ?></p> |
| 62 | 62 | |
diff --git a/src/wp-admin/includes/class-wp-privacy-requests-table.php b/src/wp-admin/includes/class-wp-privacy-requests-table.php
index 23c31b1f31..4302a7af91 100644
|
a
|
b
|
abstract class WP_Privacy_Requests_Table extends WP_List_Table { |
| 279 | 279 | 'post_status' => 'any', |
| 280 | 280 | 's' => isset( $_REQUEST['s'] ) ? sanitize_text_field( $_REQUEST['s'] ) : '', |
| 281 | 281 | ); |
| | 282 | if ( is_network_admin() ) { |
| | 283 | $args['meta_query'] = array( |
| | 284 | array( |
| | 285 | 'key' => '_wp_user_request_blog_id', |
| | 286 | 'value' => 0, |
| | 287 | ), |
| | 288 | ); |
| | 289 | } |
| | 290 | else { |
| | 291 | $args['meta_query'] = array( |
| | 292 | 'relation' => 'OR', |
| | 293 | array( |
| | 294 | 'key' => '_wp_user_request_blog_id', |
| | 295 | 'value' => get_current_blog_id(), |
| | 296 | ), |
| | 297 | // The meta key will not exist for requests submitted before 5.5. |
| | 298 | array( |
| | 299 | 'key' => '_wp_user_request_blog_id', |
| | 300 | 'compare' => 'NOT EXISTS', |
| | 301 | ), |
| | 302 | ); |
| | 303 | } |
| 282 | 304 | |
| 283 | 305 | $orderby_mapping = array( |
| 284 | 306 | 'requester' => 'post_title', |
diff --git a/src/wp-admin/includes/privacy-tools.php b/src/wp-admin/includes/privacy-tools.php
index 2d35984499..50ab29e9e3 100644
|
a
|
b
|
function _wp_personal_data_handle_actions() { |
| 141 | 141 | break; |
| 142 | 142 | } |
| 143 | 143 | |
| 144 | | $request_id = wp_create_user_request( $email_address, $action_type ); |
| | 144 | $request_id = wp_create_user_request( |
| | 145 | $email_address, |
| | 146 | $action_type, |
| | 147 | array(), |
| | 148 | is_network_admin() ? 0 : get_current_blog_id() |
| | 149 | ); |
| 145 | 150 | |
| 146 | 151 | if ( is_wp_error( $request_id ) ) { |
| 147 | 152 | add_settings_error( |
| … |
… |
function _wp_personal_data_cleanup_requests() { |
| 184 | 189 | /** This filter is documented in wp-includes/user.php */ |
| 185 | 190 | $expires = (int) apply_filters( 'user_request_key_expiration', DAY_IN_SECONDS ); |
| 186 | 191 | |
| 187 | | $requests_query = new WP_Query( |
| 188 | | array( |
| 189 | | 'post_type' => 'user_request', |
| 190 | | 'posts_per_page' => -1, |
| 191 | | 'post_status' => 'request-pending', |
| 192 | | 'fields' => 'ids', |
| 193 | | 'date_query' => array( |
| 194 | | array( |
| 195 | | 'column' => 'post_modified_gmt', |
| 196 | | 'before' => $expires . ' seconds ago', |
| 197 | | ), |
| | 192 | $args = array( |
| | 193 | 'post_type' => 'user_request', |
| | 194 | 'posts_per_page' => -1, |
| | 195 | 'post_status' => 'request-pending', |
| | 196 | 'fields' => 'ids', |
| | 197 | 'date_query' => array( |
| | 198 | array( |
| | 199 | 'column' => 'post_modified_gmt', |
| | 200 | 'before' => $expires . ' seconds ago', |
| 198 | 201 | ), |
| 199 | | ) |
| | 202 | ), |
| 200 | 203 | ); |
| | 204 | if ( is_network_admin() ) { |
| | 205 | $args['meta_query'] = array( |
| | 206 | array( |
| | 207 | 'key' => '_wp_user_request_blog_id', |
| | 208 | 'value' => 0, |
| | 209 | ), |
| | 210 | ); |
| | 211 | } |
| | 212 | else { |
| | 213 | $args['meta_query'] = array( |
| | 214 | 'relation' => 'OR', |
| | 215 | array( |
| | 216 | 'key' => '_wp_user_request_blog_id', |
| | 217 | 'value' => get_current_blog_id(), |
| | 218 | ), |
| | 219 | // The meta key will not exist for requests submitted before 5.5. |
| | 220 | array( |
| | 221 | 'key' => '_wp_user_request_blog_id', |
| | 222 | 'compare' => 'NOT EXISTS', |
| | 223 | ), |
| | 224 | ); |
| | 225 | } |
| | 226 | |
| | 227 | $requests_query = new WP_Query( $args ); |
| 201 | 228 | |
| 202 | 229 | $request_ids = $requests_query->posts; |
| 203 | 230 | |
diff --git a/src/wp-admin/network/erase-personal-data.php b/src/wp-admin/network/erase-personal-data.php
new file mode 100644
index 0000000000..30b1d5446b
|
-
|
+
|
|
| | 1 | <?php |
| | 2 | /** |
| | 3 | * Privacy tools, Erase Personal Data screen. |
| | 4 | * |
| | 5 | * @package WordPress |
| | 6 | * @subpackage Administration |
| | 7 | */ |
| | 8 | |
| | 9 | /** Load WordPress Administration Bootstrap */ |
| | 10 | require_once __DIR__ . '/admin.php'; |
| | 11 | |
| | 12 | require ABSPATH . 'wp-admin/erase-personal-data.php'; |
diff --git a/src/wp-admin/network/export-personal-data.php b/src/wp-admin/network/export-personal-data.php
new file mode 100644
index 0000000000..12a46152b1
|
-
|
+
|
|
| | 1 | <?php |
| | 2 | /** |
| | 3 | * Privacy tools, Export Personal Data screen. |
| | 4 | * |
| | 5 | * @package WordPress |
| | 6 | * @subpackage Administration |
| | 7 | */ |
| | 8 | |
| | 9 | /** Load WordPress Administration Bootstrap */ |
| | 10 | require_once __DIR__ . '/admin.php'; |
| | 11 | |
| | 12 | require ABSPATH . 'wp-admin/export-personal-data.php'; |
diff --git a/src/wp-admin/network/menu.php b/src/wp-admin/network/menu.php
index 28509b1769..de2029d7ef 100644
|
a
|
b
|
$submenu['plugins.php'][5] = array( __( 'Installed Plugins' ), 'manage_network_ |
| 106 | 106 | $submenu['plugins.php'][10] = array( _x( 'Add New', 'plugin' ), 'install_plugins', 'plugin-install.php' ); |
| 107 | 107 | $submenu['plugins.php'][15] = array( __( 'Plugin Editor' ), 'edit_plugins', 'plugin-editor.php' ); |
| 108 | 108 | |
| | 109 | $menu[21] = array( __( 'Tools' ), 'edit_posts', 'tools.php', '', 'menu-top menu-icon-tools', 'menu-tools', 'dashicons-admin-tools' ); |
| | 110 | $submenu['tools.php'][5] = array( __( 'Available Tools' ), 'edit_posts', 'tools.php' ); |
| | 111 | $submenu['tools.php'][25] = array( __( 'Export Personal Data' ), 'export_others_personal_data', 'export-personal-data.php' ); |
| | 112 | $submenu['tools.php'][30] = array( __( 'Erase Personal Data' ), 'erase_others_personal_data', 'erase-personal-data.php' ); |
| | 113 | |
| 109 | 114 | $menu[25] = array( __( 'Settings' ), 'manage_network_options', 'settings.php', '', 'menu-top menu-icon-settings', 'menu-settings', 'dashicons-admin-settings' ); |
| 110 | 115 | if ( defined( 'MULTISITE' ) && defined( 'WP_ALLOW_MULTISITE' ) && WP_ALLOW_MULTISITE ) { |
| 111 | 116 | $submenu['settings.php'][5] = array( __( 'Network Settings' ), 'manage_network_options', 'settings.php' ); |
diff --git a/src/wp-includes/admin-bar.php b/src/wp-includes/admin-bar.php
index 753e5ab5be..4dbcdad4a1 100644
|
a
|
b
|
function wp_admin_bar_my_sites_menu( $wp_admin_bar ) { |
| 556 | 556 | ); |
| 557 | 557 | } |
| 558 | 558 | |
| | 559 | if ( current_user_can( 'edit_posts' ) ) { |
| | 560 | $wp_admin_bar->add_node( |
| | 561 | array( |
| | 562 | 'parent' => 'network-admin', |
| | 563 | 'id' => 'network-admin-tools', |
| | 564 | 'title' => __( 'Tools' ), |
| | 565 | 'href' => network_admin_url( 'tools.php' ), |
| | 566 | ) |
| | 567 | ); |
| | 568 | } |
| | 569 | |
| 559 | 570 | if ( current_user_can( 'manage_network_options' ) ) { |
| 560 | 571 | $wp_admin_bar->add_node( |
| 561 | 572 | array( |
diff --git a/src/wp-includes/class-wp-user-request.php b/src/wp-includes/class-wp-user-request.php
index 7e445cfa63..8935b30f25 100644
|
a
|
b
|
final class WP_User_Request { |
| 95 | 95 | */ |
| 96 | 96 | public $confirm_key = ''; |
| 97 | 97 | |
| | 98 | /** |
| | 99 | * Blog ID the request was submitted to. |
| | 100 | * |
| | 101 | * Will be 0 if the request was submitted at the network-level. |
| | 102 | * |
| | 103 | * @since 5.5 |
| | 104 | * @var int |
| | 105 | */ |
| | 106 | public $blog_id; |
| | 107 | |
| 98 | 108 | /** |
| 99 | 109 | * Constructor. |
| 100 | 110 | * |
| … |
… |
final class WP_User_Request { |
| 114 | 124 | $this->completed_timestamp = (int) get_post_meta( $post->ID, '_wp_user_request_completed_timestamp', true ); |
| 115 | 125 | $this->request_data = json_decode( $post->post_content, true ); |
| 116 | 126 | $this->confirm_key = $post->post_password; |
| | 127 | |
| | 128 | $blog_id = get_post_meta( $post->ID, '_wp_user_request_blog_id', true ); |
| | 129 | // $blog_id will be the empty string for requests submitted prior to 5.5. |
| | 130 | $this->blog_id = '' !== $blog_id ? (int) $blog_id : get_current_blog_id(); |
| 117 | 131 | } |
| 118 | 132 | } |
diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php
index f0cc8c33bd..ddb0e2e050 100644
|
a
|
b
|
function _wp_privacy_account_request_confirmed_message( $request_id ) { |
| 3554 | 3554 | * users on the site, or guests without a user account. |
| 3555 | 3555 | * |
| 3556 | 3556 | * @since 4.9.6 |
| | 3557 | * @since 5.5.0 Added `$blog_id` parameter. |
| 3557 | 3558 | * |
| 3558 | 3559 | * @param string $email_address User email address. This can be the address of a registered or non-registered user. |
| 3559 | 3560 | * @param string $action_name Name of the action that is being confirmed. Required. |
| 3560 | 3561 | * @param array $request_data Misc data you want to send with the verification request and pass to the actions once the request is confirmed. |
| | 3562 | * @param int|null $blog_id Blog ID to create request in. Use 0 for a network-level requests. Default is null, indicating the current blog ID. |
| 3561 | 3563 | * @return int|WP_Error Returns the request ID if successful, or a WP_Error object on failure. |
| 3562 | 3564 | */ |
| 3563 | | function wp_create_user_request( $email_address = '', $action_name = '', $request_data = array() ) { |
| | 3565 | function wp_create_user_request( $email_address = '', $action_name = '', $request_data = array(), $blog_id = null ) { |
| 3564 | 3566 | $email_address = sanitize_email( $email_address ); |
| 3565 | 3567 | $action_name = sanitize_key( $action_name ); |
| | 3568 | $blog_id = null === $blog_id ? get_current_blog_id() : (int) $blog_id; |
| 3566 | 3569 | |
| 3567 | 3570 | if ( ! is_email( $email_address ) ) { |
| 3568 | 3571 | return new WP_Error( 'invalid_email', __( 'Invalid email address.' ) ); |
| … |
… |
function wp_create_user_request( $email_address = '', $action_name = '', $reques |
| 3572 | 3575 | return new WP_Error( 'invalid_action', __( 'Invalid action name.' ) ); |
| 3573 | 3576 | } |
| 3574 | 3577 | |
| | 3578 | if ( $blog_id && ! get_site( $blog_id ) ) { |
| | 3579 | return new WP_Error( 'invalid_blog_id', __( 'Invalid blog ID.' ) ); |
| | 3580 | } |
| | 3581 | |
| 3575 | 3582 | $user = get_user_by( 'email', $email_address ); |
| 3576 | 3583 | $user_id = $user && ! is_wp_error( $user ) ? $user->ID : 0; |
| 3577 | 3584 | |
| 3578 | 3585 | // Check for duplicates. |
| 3579 | | $requests_query = new WP_Query( |
| 3580 | | array( |
| 3581 | | 'post_type' => 'user_request', |
| 3582 | | 'post_name__in' => array( $action_name ), // Action name stored in post_name column. |
| 3583 | | 'title' => $email_address, // Email address stored in post_title column. |
| 3584 | | 'post_status' => array( |
| 3585 | | 'request-pending', |
| 3586 | | 'request-confirmed', |
| | 3586 | $args = array( |
| | 3587 | 'post_type' => 'user_request', |
| | 3588 | 'post_name__in' => array( $action_name ), // Action name stored in post_name column. |
| | 3589 | 'title' => $email_address, // Email address stored in post_title column. |
| | 3590 | 'post_status' => array( |
| | 3591 | 'request-pending', |
| | 3592 | 'request-confirmed', |
| | 3593 | ), |
| | 3594 | 'fields' => 'ids', |
| | 3595 | 'meta_query' => array( |
| | 3596 | array( |
| | 3597 | 'key' => '_wp_user_request_blog_id', |
| | 3598 | 'value' => $blog_id, |
| 3587 | 3599 | ), |
| 3588 | | 'fields' => 'ids', |
| 3589 | | ) |
| | 3600 | ), |
| 3590 | 3601 | ); |
| | 3602 | if ( $blog_id ) { |
| | 3603 | // add a check for requests submitted prior to 5.5. |
| | 3604 | $args['meta_query']['relation'] = 'OR'; |
| | 3605 | $args['meta_query'][] = array( |
| | 3606 | 'key' => '_wp_user_request_blog_id', |
| | 3607 | 'compare' => 'NOT EXISTS', |
| | 3608 | ); |
| | 3609 | } |
| | 3610 | |
| | 3611 | $requests_query = new WP_Query( $args ); |
| 3591 | 3612 | |
| 3592 | 3613 | if ( $requests_query->found_posts ) { |
| 3593 | 3614 | return new WP_Error( 'duplicate_request', __( 'An incomplete request for this email address already exists.' ) ); |
| … |
… |
function wp_create_user_request( $email_address = '', $action_name = '', $reques |
| 3603 | 3624 | 'post_type' => 'user_request', |
| 3604 | 3625 | 'post_date' => current_time( 'mysql', false ), |
| 3605 | 3626 | 'post_date_gmt' => current_time( 'mysql', true ), |
| | 3627 | 'meta_input' => array( |
| | 3628 | '_wp_user_request_blog_id' => $blog_id, |
| | 3629 | ), |
| 3606 | 3630 | ), |
| 3607 | 3631 | true |
| 3608 | 3632 | ); |