Ticket #43738: 43738-2.diff
File 43738-2.diff, 17.0 KB (added by , 3 weeks ago) |
---|
-
src/wp-admin/erase-personal-data.php
From f2c1d99e225dd91932980d7d69cd773a24587a44 Mon Sep 17 00:00:00 2001 From: Paul Biron <paul@sparrowhawkcomputing.com> Date: Sat, 28 Jun 2025 16:21:32 -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 | 54 ++++++++++++---- 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 | 62 +++++++++++++------ 10 files changed, 163 insertions(+), 33 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 c96d80a9b5..716ba4580e 100644
a b require_once ABSPATH . 'wp-admin/admin-header.php'; 110 110 111 111 <?php settings_errors(); ?> 112 112 113 <form action="<?php echo esc_url( admin_url( 'erase-personal-data.php' ) ); ?>" method="post" class="wp-privacy-request-form">113 <form action="<?php echo esc_url( self_admin_url( 'erase-personal-data.php' ) ); ?>" method="post" class="wp-privacy-request-form"> 114 114 <h2><?php esc_html_e( 'Add Data Erasure Request' ); ?></h2> 115 115 <div class="wp-privacy-request-form-field"> 116 116 <table class="form-table"> -
src/wp-admin/export-personal-data.php
diff --git a/src/wp-admin/export-personal-data.php b/src/wp-admin/export-personal-data.php index 64b9653c3c..6cc7a644a1 100644
a b require_once ABSPATH . 'wp-admin/admin-header.php'; 110 110 111 111 <?php settings_errors(); ?> 112 112 113 <form action="<?php echo esc_url( admin_url( 'export-personal-data.php' ) ); ?>" method="post" class="wp-privacy-request-form">113 <form action="<?php echo esc_url( self_admin_url( 'export-personal-data.php' ) ); ?>" method="post" class="wp-privacy-request-form"> 114 114 <h2><?php esc_html_e( 'Add Data Export Request' ); ?></h2> 115 115 <div class="wp-privacy-request-form-field"> 116 116 <table class="form-table"> -
src/wp-admin/includes/class-wp-privacy-requests-table.php
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 18a82590be..3b7b35023b 100644
a b abstract class WP_Privacy_Requests_Table extends WP_List_Table { 375 375 'post_status' => 'any', 376 376 's' => isset( $_REQUEST['s'] ) ? sanitize_text_field( $_REQUEST['s'] ) : '', 377 377 ); 378 if ( is_network_admin() ) { 379 $args['meta_query'] = array( 380 array( 381 'key' => '_wp_user_request_blog_id', 382 'value' => 0, 383 ), 384 ); 385 } 386 else { 387 $args['meta_query'] = array( 388 'relation' => 'OR', 389 array( 390 'key' => '_wp_user_request_blog_id', 391 'value' => get_current_blog_id(), 392 ), 393 // The meta key will not exist for requests submitted before 5.5. 394 array( 395 'key' => '_wp_user_request_blog_id', 396 'compare' => 'NOT EXISTS', 397 ), 398 ); 399 } 378 400 379 401 $orderby_mapping = array( 380 402 'requester' => 'post_title', -
src/wp-admin/includes/privacy-tools.php
diff --git a/src/wp-admin/includes/privacy-tools.php b/src/wp-admin/includes/privacy-tools.php index d5853185eb..015acf587e 100644
a b function _wp_personal_data_handle_actions() { 146 146 break; 147 147 } 148 148 149 $request_id = wp_create_user_request( $email_address, $action_type, array(), $status ); 149 $request_id = wp_create_user_request( 150 $email_address, 151 $action_type, 152 array(), 153 is_network_admin() ? 0 : get_current_blog_id() 154 ); 155 150 156 $message = ''; 151 157 152 158 if ( is_wp_error( $request_id ) ) { … … function _wp_personal_data_cleanup_requests() { 196 202 /** This filter is documented in wp-includes/user.php */ 197 203 $expires = (int) apply_filters( 'user_request_key_expiration', DAY_IN_SECONDS ); 198 204 199 $requests_query = new WP_Query( 200 array( 201 'post_type' => 'user_request', 202 'posts_per_page' => -1, 203 'post_status' => 'request-pending', 204 'fields' => 'ids', 205 'date_query' => array( 206 array( 207 'column' => 'post_modified_gmt', 208 'before' => $expires . ' seconds ago', 209 ), 205 $args = array( 206 'post_type' => 'user_request', 207 'posts_per_page' => -1, 208 'post_status' => 'request-pending', 209 'fields' => 'ids', 210 'date_query' => array( 211 array( 212 'column' => 'post_modified_gmt', 213 'before' => $expires . ' seconds ago', 210 214 ), 211 ) 215 ), 212 216 ); 217 if ( is_network_admin() ) { 218 $args['meta_query'] = array( 219 array( 220 'key' => '_wp_user_request_blog_id', 221 'value' => 0, 222 ), 223 ); 224 } 225 else { 226 $args['meta_query'] = array( 227 'relation' => 'OR', 228 array( 229 'key' => '_wp_user_request_blog_id', 230 'value' => get_current_blog_id(), 231 ), 232 // The meta key will not exist for requests submitted before 5.5. 233 array( 234 'key' => '_wp_user_request_blog_id', 235 'compare' => 'NOT EXISTS', 236 ), 237 ); 238 } 239 240 $requests_query = new WP_Query( $args ); 213 241 214 242 $request_ids = $requests_query->posts; 215 243 -
new file src/wp-admin/network/erase-personal-data.php
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'; -
new file src/wp-admin/network/export-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'; -
src/wp-admin/network/menu.php
diff --git a/src/wp-admin/network/menu.php b/src/wp-admin/network/menu.php index ee987c83c6..83cd06e631 100644
a b $submenu['plugins.php'][5] = array( __( 'Installed Plugins' ), 'manage_network_ 111 111 $submenu['plugins.php'][10] = array( __( 'Add Plugin' ), 'install_plugins', 'plugin-install.php' ); 112 112 $submenu['plugins.php'][15] = array( __( 'Plugin File Editor' ), 'edit_plugins', 'plugin-editor.php' ); 113 113 114 $menu[21] = array( __( 'Tools' ), 'edit_posts', 'tools.php', '', 'menu-top menu-icon-tools', 'menu-tools', 'dashicons-admin-tools' ); 115 $submenu['tools.php'][5] = array( __( 'Available Tools' ), 'edit_posts', 'tools.php' ); 116 $submenu['tools.php'][25] = array( __( 'Export Personal Data' ), 'export_others_personal_data', 'export-personal-data.php' ); 117 $submenu['tools.php'][30] = array( __( 'Erase Personal Data' ), 'erase_others_personal_data', 'erase-personal-data.php' ); 118 114 119 $menu[25] = array( __( 'Settings' ), 'manage_network_options', 'settings.php', '', 'menu-top menu-icon-settings', 'menu-settings', 'dashicons-admin-settings' ); 115 120 if ( defined( 'MULTISITE' ) && defined( 'WP_ALLOW_MULTISITE' ) && WP_ALLOW_MULTISITE ) { 116 121 $submenu['settings.php'][5] = array( __( 'Network Settings' ), 'manage_network_options', 'settings.php' ); -
src/wp-includes/admin-bar.php
diff --git a/src/wp-includes/admin-bar.php b/src/wp-includes/admin-bar.php index 5fe00e9801..bafacab415 100644
a b function wp_admin_bar_my_sites_menu( $wp_admin_bar ) { 646 646 ); 647 647 } 648 648 649 if ( current_user_can( 'edit_posts' ) ) { 650 $wp_admin_bar->add_node( 651 array( 652 'parent' => 'network-admin', 653 'id' => 'network-admin-tools', 654 'title' => __( 'Tools' ), 655 'href' => network_admin_url( 'tools.php' ), 656 ) 657 ); 658 } 659 649 660 if ( current_user_can( 'manage_network_options' ) ) { 650 661 $wp_admin_bar->add_node( 651 662 array( -
src/wp-includes/class-wp-user-request.php
diff --git a/src/wp-includes/class-wp-user-request.php b/src/wp-includes/class-wp-user-request.php index dc8ca7cdbd..1ca1b9c583 100644
a b final class WP_User_Request { 98 98 */ 99 99 public $confirm_key = ''; 100 100 101 /** 102 * Blog ID the request was submitted to. 103 * 104 * Will be 0 if the request was submitted at the network-level. 105 * 106 * @since 5.5 107 * @var int 108 */ 109 public $blog_id; 110 101 111 /** 102 112 * Constructor. 103 113 * … … final class WP_User_Request { 117 127 $this->completed_timestamp = (int) get_post_meta( $post->ID, '_wp_user_request_completed_timestamp', true ); 118 128 $this->request_data = json_decode( $post->post_content, true ); 119 129 $this->confirm_key = $post->post_password; 130 131 $blog_id = get_post_meta( $post->ID, '_wp_user_request_blog_id', true ); 132 // $blog_id will be the empty string for requests submitted prior to 5.5. 133 $this->blog_id = '' !== $blog_id ? (int) $blog_id : get_current_blog_id(); 120 134 } 121 135 } -
src/wp-includes/user.php
diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index ecd22ab38c..8c58139452 100644
a b function _wp_privacy_account_request_confirmed_message( $request_id ) { 4698 4698 * users on the site, or guests without a user account. 4699 4699 * 4700 4700 * @since 4.9.6 4701 * 4701 4702 * @since 5.7.0 Added the `$status` parameter. 4703 * @since x.y.z Added `$blog_id` parameter. 4704 4702 4705 * 4703 * @param string $email_address User email address. This can be the address of a registered 4704 * or non-registered user. 4705 * @param string $action_name Name of the action that is being confirmed. Required. 4706 * @param array $request_data Misc data you want to send with the verification request and pass 4707 * to the actions once the request is confirmed. 4708 * @param string $status Optional request status (pending or confirmed). Default 'pending'. 4709 * @return int|WP_Error Returns the request ID if successful, or a WP_Error object on failure. 4706 * @param string $email_address User email address. This can be the address of a registered 4707 * or non-registered user. 4708 * @param string $action_name Name of the action that is being confirmed. Required. 4709 * @param array $request_data Misc data you want to send with the verification request and pass 4710 * to the actions once the request is confirmed. 4711 * @param string $status Optional request status (pending or confirmed). Default 'pending'. 4712 * @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. 4713 * @return int|WP_Error Returns the request ID if successful, or a WP_Error object on failure. 4710 4714 */ 4711 function wp_create_user_request( $email_address = '', $action_name = '', $request_data = array(), $status = 'pending' ) {4715 function wp_create_user_request( $email_address = '', $action_name = '', $request_data = array(), $status = 'pending', $blog_id = null ) { 4712 4716 $email_address = sanitize_email( $email_address ); 4713 4717 $action_name = sanitize_key( $action_name ); 4718 $blog_id = null === $blog_id ? get_current_blog_id() : (int) $blog_id; 4714 4719 4715 4720 if ( ! is_email( $email_address ) ) { 4716 4721 return new WP_Error( 'invalid_email', __( 'Invalid email address.' ) ); … … function wp_create_user_request( $email_address = '', $action_name = '', $reques 4724 4729 return new WP_Error( 'invalid_status', __( 'Invalid request status.' ) ); 4725 4730 } 4726 4731 4732 if ( $blog_id && ! get_site( $blog_id ) ) { 4733 return new WP_Error( 'invalid_blog_id', __( 'Invalid blog ID.' ) ); 4734 } 4735 4727 4736 $user = get_user_by( 'email', $email_address ); 4728 4737 $user_id = $user && ! is_wp_error( $user ) ? $user->ID : 0; 4729 4738 4730 4739 // Check for duplicates. 4731 $requests_query = new WP_Query( 4732 array( 4733 'post_type' => 'user_request', 4734 'post_name__in' => array( $action_name ), // Action name stored in post_name column. 4735 'title' => $email_address, // Email address stored in post_title column. 4736 'post_status' => array( 4737 'request-pending', 4738 'request-confirmed', 4740 $args = array( 4741 'post_type' => 'user_request', 4742 'post_name__in' => array( $action_name ), // Action name stored in post_name column. 4743 'title' => $email_address, // Email address stored in post_title column. 4744 'post_status' => array( 4745 'request-pending', 4746 'request-confirmed', 4747 ), 4748 'fields' => 'ids', 4749 'meta_query' => array( 4750 array( 4751 'key' => '_wp_user_request_blog_id', 4752 'value' => $blog_id, 4739 4753 ), 4740 'fields' => 'ids', 4741 ) 4754 ), 4742 4755 ); 4756 if ( $blog_id ) { 4757 // add a check for requests submitted prior to 5.5. 4758 $args['meta_query']['relation'] = 'OR'; 4759 $args['meta_query'][] = array( 4760 'key' => '_wp_user_request_blog_id', 4761 'compare' => 'NOT EXISTS', 4762 ); 4763 } 4764 4765 $requests_query = new WP_Query( $args ); 4743 4766 4744 4767 if ( $requests_query->found_posts ) { 4745 4768 return new WP_Error( 'duplicate_request', __( 'An incomplete personal data request for this email address already exists.' ) ); … … function wp_create_user_request( $email_address = '', $action_name = '', $reques 4755 4778 'post_type' => 'user_request', 4756 4779 'post_date' => current_time( 'mysql', false ), 4757 4780 'post_date_gmt' => current_time( 'mysql', true ), 4781 'meta_input' => array( 4782 '_wp_user_request_blog_id' => $blog_id, 4783 ), 4758 4784 ), 4759 4785 true 4760 4786 ); -
new file src/wp-admin/network/tools.php
-- 2.37.0.windows.1 From 774d4585663ef9049b4ef6477a4c833d2196ae84 Mon Sep 17 00:00:00 2001 From: Paul Biron <paul@sparrowhawkcomputing.com> Date: Sun, 29 Jun 2025 12:03:15 -0600 Subject: [PATCH] Add an empty network/tools.php screen. --- src/wp-admin/network/tools.php | 58 ++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/wp-admin/network/tools.php diff --git a/src/wp-admin/network/tools.php b/src/wp-admin/network/tools.php new file mode 100644 index 0000000000..911c61ff2c
- + 1 <?php 2 /** 3 * Multisite Tools Administration Screen. 4 * 5 * @package WordPress 6 * @subpackage Administration 7 */ 8 9 if ( isset( $_GET['page'] ) && ! empty( $_POST ) ) { 10 // Ensure POST-ing to `tools.php?page=export_personal_data` and `tools.php?page=remove_personal_data` 11 // continues to work after creating the new files for exporting and erasing of personal data. 12 if ( 'export_personal_data' === $_GET['page'] ) { 13 require_once ABSPATH . 'wp-admin/network/export-personal-data.php'; 14 return; 15 } elseif ( 'remove_personal_data' === $_GET['page'] ) { 16 require_once ABSPATH . 'wp-admin/network/erase-personal-data.php'; 17 return; 18 } 19 } 20 21 if ( isset( $_GET['page'] ) ) { 22 // These were also moved to files in WP 5.3. 23 if ( 'export_personal_data' === $_GET['page'] ) { 24 require_once dirname( __DIR__ ) . '/wp-load.php'; 25 wp_redirect( admin_url( 'network/export-personal-data.php' ), 301 ); 26 exit; 27 } elseif ( 'remove_personal_data' === $_GET['page'] ) { 28 require_once dirname( __DIR__ ) . '/wp-load.php'; 29 wp_redirect( admin_url( 'network/erase-personal-data.php' ), 301 ); 30 exit; 31 } 32 } 33 34 /** WordPress Administration Bootstrap */ 35 require_once __DIR__ . '/admin.php'; 36 37 // Used in the HTML title tag. 38 $title = __( 'Tools' ); 39 40 require_once ABSPATH . 'wp-admin/admin-header.php'; 41 42 ?> 43 <div class="wrap"> 44 <h1><?php echo esc_html( $title ); ?></h1> 45 <?php 46 47 /** 48 * Fires at the end of the Tools Administration screen. 49 * 50 * @since x.y.z 51 */ 52 do_action( 'network_tool_box' ); 53 54 ?> 55 </div> 56 <?php 57 58 require_once ABSPATH . 'wp-admin/admin-footer.php'; -
src/wp-admin/includes/privacy-tools.php
-- 2.37.0.windows.1 From 2981b870b052c5bed48dde535db37e2e6781d675 Mon Sep 17 00:00:00 2001 From: Paul Biron <paul@sparrowhawkcomputing.com> Date: Wed, 2 Jul 2025 11:53:56 -0600 Subject: [PATCH] Bug fix: in wp_personal_data_handle_actions(), pass $status as the 4th param to wp_create_user_request(). This was accidentally left out when refreshing the patch the other day. --- src/wp-admin/includes/privacy-tools.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wp-admin/includes/privacy-tools.php b/src/wp-admin/includes/privacy-tools.php index 015acf587e..27844a3d9b 100644
a b function _wp_personal_data_handle_actions() { 150 150 $email_address, 151 151 $action_type, 152 152 array(), 153 $status, 153 154 is_network_admin() ? 0 : get_current_blog_id() 154 155 ); 155 156