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 | ); |