diff --git a/src/wp-admin/includes/admin-filters.php b/src/wp-admin/includes/admin-filters.php index 6ce59772c5..30da2d0a56 100644 --- a/src/wp-admin/includes/admin-filters.php +++ b/src/wp-admin/includes/admin-filters.php @@ -132,6 +132,10 @@ add_action( 'upgrader_process_complete', 'wp_version_check', 10, 0 ); add_action( 'upgrader_process_complete', 'wp_update_plugins', 10, 0 ); add_action( 'upgrader_process_complete', 'wp_update_themes', 10, 0 ); +// Privacy hooks +add_filter( 'wp_privacy_personal_data_export_page', 'wp_privacy_process_personal_data_export_page', 10, 6 ); +add_action( 'wp_privacy_personal_data_export_file', 'wp_privacy_generate_personal_data_export_file', 10 ); + // Privacy policy text changes check. add_action( 'admin_init', array( 'WP_Privacy_Policy_Content', 'text_change_check' ), 20 ); @@ -143,4 +147,3 @@ add_action( 'admin_init', array( 'WP_Privacy_Policy_Content', 'add_suggested_con // Stop checking for text changes after the policy page is updated. add_action( 'post_updated', array( 'WP_Privacy_Policy_Content', '_policy_page_updated' ) ); - diff --git a/src/wp-admin/includes/ajax-actions.php b/src/wp-admin/includes/ajax-actions.php index b8b13ee75c..4f30007f1a 100644 --- a/src/wp-admin/includes/ajax-actions.php +++ b/src/wp-admin/includes/ajax-actions.php @@ -4327,16 +4327,39 @@ function wp_ajax_edit_theme_plugin_file() { } } +/** + * Ajax handler for exporting a user's personal data. + * + * @since 4.9.6 + */ function wp_ajax_wp_privacy_export_personal_data() { - check_ajax_referer( 'wp-privacy-export-personal-data', 'security' ); + $request_id = (int) $_POST['id']; + + if ( empty( $request_id ) ) { + wp_send_json_error( __( 'Error: Invalid request ID.' ) ); + } if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( __( 'Error: Invalid request.' ) ); } - $email_address = sanitize_text_field( $_POST['email'] ); + check_ajax_referer( 'wp-privacy-export-personal-data-' . $request_id, 'security' ); + + // Get the request data. + $request = wp_get_user_request_data( $request_id ); + + if ( ! $request || 'export_personal_data' !== $request->action_name ) { + wp_send_json_error( __( 'Error: Invalid request type.' ) ); + } + + $email_address = $request->email; + if ( ! is_email( $email_address ) ) { + wp_send_json_error( __( 'Error: A valid email address must be given.' ) ); + } + $exporter_index = (int) $_POST['exporter']; $page = (int) $_POST['page']; + $send_as_email = isset( $_POST['sendAsEmail'] ) ? ( "true" === $_POST['sendAsEmail'] ) : false; /** * Filters the array of exporter callbacks. @@ -4348,8 +4371,8 @@ function wp_ajax_wp_privacy_export_personal_data() { * [ * callback string Callable exporter that accepts an email address and * a page and returns an array of name => value - * pairs of personal data - * exporter_friendly_name string Translated user facing friendly name for the exporter + * pairs of personal data. + * exporter_friendly_name string Translated user facing friendly name for the exporter. * ] * } */ @@ -4375,26 +4398,20 @@ function wp_ajax_wp_privacy_export_personal_data() { wp_send_json_error( 'Page index cannot be less than one.' ); } - // Surprisingly, email addresses can contain mutli-byte characters now - $email_address = trim( mb_strtolower( $email_address ) ); - - if ( ! is_email( $email_address ) ) { - wp_send_json_error( 'A valid email address must be given.' ); - } - $exporter = $exporters[ $index ]; + if ( ! is_array( $exporter ) ) { wp_send_json_error( "Expected an array describing the exporter at index {$exporter_index}." ); } - if ( ! array_key_exists( 'callback', $exporter ) ) { - wp_send_json_error( "Exporter array at index {$exporter_index} does not include a callback." ); - } - if ( ! is_callable( $exporter['callback'] ) ) { - wp_send_json_error( "Exporter callback at index {$exporter_index} is not a valid callback." ); - } if ( ! array_key_exists( 'exporter_friendly_name', $exporter ) ) { wp_send_json_error( "Exporter array at index {$exporter_index} does not include a friendly name." ); } + if ( ! array_key_exists( 'callback', $exporter ) ) { + wp_send_json_error( "Exporter does not include a callback: {$exporter['exporter_friendly_name']}." ); + } + if ( ! is_callable( $exporter['callback'] ) ) { + wp_send_json_error( "Exporter callback is not a valid callback: {$exporter['exporter_friendly_name']}." ); + } $callback = $exporters[ $index ]['callback']; $exporter_friendly_name = $exporters[ $index ]['exporter_friendly_name']; @@ -4417,7 +4434,7 @@ function wp_ajax_wp_privacy_export_personal_data() { wp_send_json_error( "Expected done (boolean) in response array from exporter: {$exporter_friendly_name}." ); } } else { - // No exporters, so we're done + // No exporters, so we're done. $response = array( 'data' => array(), 'done' => true, @@ -4435,8 +4452,11 @@ function wp_ajax_wp_privacy_export_personal_data() { * @param int $exporter_index The index of the exporter that provided this data. * @param string $email_address The email address associated with this personal data. * @param int $page The zero-based page for this response. + * @param int $request_id The privacy request post ID associated with this request. + * @param bool $send_as_email Whether the final results of the export should be emailed to the user. */ - $response = apply_filters( 'wp_privacy_personal_data_export_page', $response, $exporter_index, $email_address, $page ); + $response = apply_filters( 'wp_privacy_personal_data_export_page', $response, $exporter_index, $email_address, $page, $request_id, $send_as_email ); + if ( is_wp_error( $response ) ) { wp_send_json_error( $response ); } @@ -4462,7 +4482,7 @@ function wp_ajax_wp_privacy_erase_personal_data() { check_ajax_referer( 'wp-privacy-erase-personal-data-' . $request_id, 'security' ); - // Find the request CPT + // Get the request data. $request = wp_get_user_request_data( $request_id ); if ( ! $request || 'remove_personal_data' !== $request->action_name ) { diff --git a/src/wp-admin/includes/file.php b/src/wp-admin/includes/file.php index 34f1146b31..8c8d6bc8d2 100644 --- a/src/wp-admin/includes/file.php +++ b/src/wp-admin/includes/file.php @@ -1934,3 +1934,432 @@ function wp_print_request_filesystem_credentials_modal() { array( + 'href' => array(), + 'target' => array() + ), + 'br' => array() + ); + $allowed_protocols = array( 'http', 'https' ); + $group_html = ''; + + $group_html .= '

' . esc_html( $group_data['group_label'] ) . '

'; + $group_html .= '
'; + + foreach ( (array) $group_data['items'] as $group_item_id => $group_item_data ) { + $group_html .= ''; + $group_html .= ''; + + foreach ( (array) $group_item_data as $group_item_datum ) { + $group_html .= ''; + $group_html .= ''; + $group_html .= ''; + $group_html .= ''; + } + + $group_html .= ''; + $group_html .= '
' . esc_html( $group_item_datum['name'] ) . '' . wp_kses( $group_item_datum['value'], $allowed_tags, $allowed_protocols ) . '
'; + } + + $group_html .= '
'; + + return $group_html; +} + +/** + * Generate the personal data export file. + * + * @since 4.9.6 + * + * @param int $request_id The export request ID. + */ +function wp_privacy_generate_personal_data_export_file( $request_id ) { + // Maybe make this a cron job instead. + wp_privacy_delete_old_export_files(); + + if ( ! class_exists( 'ZipArchive' ) ) { + wp_send_json_error( __( 'Unable to generate export file. ZipArchive not available.' ) ); + } + + // Get the request data. + $request = wp_get_user_request_data( $request_id ); + + if ( ! $request || 'export_personal_data' !== $request->action_name ) { + wp_send_json_error( __( 'Invalid request ID when generating export file' ) ); + } + + $email_address = $request->email; + + if ( ! is_email( $email_address ) ) { + wp_send_json_error( __( 'Invalid email address when generating export file' ) ); + } + + // Create the exports folder if needed. + $upload_dir = wp_upload_dir(); + $exports_dir = trailingslashit( $upload_dir['basedir'] . '/exports' ); + $exports_url = trailingslashit( $upload_dir['baseurl'] . '/exports' ); + + $result = wp_mkdir_p( $exports_dir ); + if ( is_wp_error( $result ) ) { + wp_send_json_error( $result->get_error_message() ); + } + + // Protect export folder from browsing. + $index_pathname = $exports_dir . 'index.html'; + if ( ! file_exists( $index_pathname ) ) { + $file = fopen( $index_pathname, 'w' ); + if ( false === $file ) { + wp_send_json_error( __( 'Unable to protect export folder from browsing' ) ); + } + fwrite( $file, 'Silence is golden.' ); + fclose( $file ); + } + + $stripped_email = str_replace( '@', '-at-', $email_address ); + $stripped_email = sanitize_title( $stripped_email ); // slugify the email address + $obscura = md5( rand() ); + $file_basename = 'wp-personal-data-file-' . $stripped_email . '-' . $obscura; + $html_report_filename = $file_basename . '.html'; + $html_report_pathname = $exports_dir . $html_report_filename; + $file = fopen( $html_report_pathname, 'w' ); + if ( false === $file ) { + wp_send_json_error( __( 'Unable to open export file (HTML report) for writing' ) ); + } + + $title = sprintf( + // translators: %s Users e-mail address. + __( 'Personal Data Export for %s' ), + $email_address + ); + + // Open HTML. + fwrite( $file, "\n" ); + fwrite( $file, "\n" ); + + // Head. + fwrite( $file, "\n" ); + fwrite( $file, "\n" ); + fwrite( $file, "" ); + fwrite( $file, "" ); + fwrite( $file, esc_html( $title ) ); + fwrite( $file, "" ); + fwrite( $file, "\n" ); + + // Body. + fwrite( $file, "\n" ); + + // Heading. + fwrite( $file, "

" . esc_html__( 'Personal Data Export' ) . "

" ); + + // And now, all the Groups. + $groups = get_post_meta( $request_id, '_export_data_grouped', true ); + + // First, build an "About" group on the fly for this report. + $about_group = array( + 'group_label' => __( 'About' ), + 'items' => array( + 'about-1' => array( + array( + 'name' => __( 'Report generated for' ), + 'value' => $email_address, + ), + array( + 'name' => __( 'For site' ), + 'value' => get_bloginfo( 'name' ), + ), + array( + 'name' => __( 'At URL' ), + 'value' => get_bloginfo( 'url' ), + ), + array( + 'name' => __( 'On' ), + 'value' => current_time( 'mysql' ), + ), + ), + ), + ); + + // Merge in the special about group. + $groups = array_merge( array( 'about' => $about_group ), $groups ); + + // Now, iterate over every group in $groups and have the formatter render it in HTML. + foreach ( (array) $groups as $group_id => $group_data ) { + fwrite( $file, wp_privacy_generate_personal_data_export_group_html( $group_data ) ); + } + + fwrite( $file, "\n" ); + + // Close HTML. + fwrite( $file, "\n" ); + fclose( $file ); + + // Now, generate the ZIP. + $archive_filename = $file_basename . '.zip'; + $archive_pathname = $exports_dir . $archive_filename; + $archive_url = $exports_url . $archive_filename; + + $zip = new ZipArchive; + + if ( TRUE === $zip->open( $archive_pathname, ZipArchive::CREATE ) ) { + $zip->addFile( $html_report_pathname, 'index.html' ); + $zip->close(); + } else { + wp_send_json_error( __( 'Unable to open export file (archive) for writing' ) ); + } + + // And remove the HTML file. + unlink( $html_report_pathname ); + + // Save the export file in the request. + update_post_meta( $request_id, '_export_file_url', $archive_url ); + update_post_meta( $request_id, '_export_file_path', $archive_pathname ); +} + +/** + * Send an email to the user with a link to the personal data export file + * + * @since 4.9.6 + * + * @param int $request_id The request ID for this personal data export. + * @return true|WP_Error True on success or `WP_Error` on failure. + */ +function wp_privacy_send_personal_data_export_email( $request_id ) { + // Get the request data. + $request = wp_get_user_request_data( $request_id ); + + if ( ! $request || 'export_personal_data' !== $request->action_name ) { + return new WP_Error( 'invalid', __( 'Invalid request ID when sending personal data export email.' ) ); + } + +/* translators: Do not translate LINK, EMAIL, SITENAME, SITEURL: those are placeholders. */ +$email_text = __( +'Howdy, + +Your request for an export of personal data has been completed. You may +download your personal data by clicking on the link below. This link is +good for the next 3 days. + +###LINK### + +This email has been sent to ###EMAIL###. + +Regards, +All at ###SITENAME### +###SITEURL###' +); + + /** + * Filters the text of the email sent with a personal data export file. + * + * The following strings have a special meaning and will get replaced dynamically: + * ###LINK### URL of the personal data export file for the user. + * ###EMAIL### The email we are sending to. + * ###SITENAME### The name of the site. + * ###SITEURL### The URL to the site. + * + * @since 4.9.6 + * + * @param string $email_text Text in the email. + * @param int $request_id The request ID for this personal data export. + */ + $content = apply_filters( 'wp_privacy_personal_data_email_content', $email_text, $request_id ); + + $email_address = $request->email; + $export_file_url = get_post_meta( $request_id, '_export_file_url', true ); + $site_name = is_multisite() ? get_site_option( 'site_name' ) : get_option( 'blogname' ); + $site_url = network_home_url(); + + $content = str_replace( '###LINK###', esc_url_raw( $export_file_url ), $content ); + $content = str_replace( '###EMAIL###', $email_address, $content ); + $content = str_replace( '###SITENAME###', wp_specialchars_decode( $site_name, ENT_QUOTES ), $content ); + $content = str_replace( '###SITEURL###', esc_url_raw( $site_url ), $content ); + + $mail_success = wp_mail( + $email_address, + sprintf( + __( '[%s] Personal Data Export' ), + wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ) + ), + $content + ); + + if ( ! $mail_success ) { + return new WP_Error( 'error', __( 'Unable to send personal data export email.' ) ); + } + + return true; +} + +/** + * Intercept personal data exporter page ajax responses in order to assemble the personal data export file. + * @see wp_privacy_personal_data_export_page + * @since 4.9.6 + * + * @param array $response The response from the personal data exporter for the given page. + * @param int $exporter_index The index of the personal data exporter. Begins at 1. + * @param string $email_address The email address of the user whose personal data this is. + * @param int $page The page of personal data for this exporter. Begins at 1. + * @param int $request_id The request ID for this personal data export. + * @param bool $send_as_email Whether the final results of the export should be emailed to the user. + * @return array The filtered response. + */ +function wp_privacy_process_personal_data_export_page( $response, $exporter_index, $email_address, $page, $request_id, $send_as_email ) { + /* Do some simple checks on the shape of the response from the exporter. + * If the exporter response is malformed, don't attempt to consume it - let it + * pass through to generate a warning to the user by default ajax processing. + */ + if ( ! is_array( $response ) ) { + return $response; + } + + if ( ! array_key_exists( 'done', $response ) ) { + return $response; + } + + if ( ! array_key_exists( 'data', $response ) ) { + return $response; + } + + if ( ! is_array( $response['data'] ) ) { + return $response; + } + + // Get the request data. + $request = wp_get_user_request_data( $request_id ); + + if ( ! $request || 'export_personal_data' !== $request->action_name ) { + wp_send_json_error( __( 'Invalid request ID when merging exporter data' ) ); + } + + $export_data = array(); + + // First exporter, first page? Reset the report data accumulation array. + if ( 1 === $exporter_index && 1 === $page ) { + update_post_meta( $request_id, '_export_data_raw', $export_data ); + } else { + $export_data = get_post_meta( $request_id, '_export_data_raw', true ); + } + + // Now, merge the data from the exporter response into the data we have accumulated already. + $export_data = array_merge( $export_data, $response['data'] ); + update_post_meta( $request_id, '_export_data_raw', $export_data ); + + // If we are not yet on the last page of the last exporter, return now. + $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() ); + $is_last_exporter = $exporter_index === count( $exporters ); + $exporter_done = $response['done']; + if ( ! $is_last_exporter || ! $exporter_done ) { + return $response; + } + + // Last exporter, last page - let's prepare the export file. + + // First we need to re-organize the raw data hierarchically in groups and items. + $groups = array(); + foreach ( (array) $export_data as $export_datum ) { + $group_id = $export_datum['group_id']; + $group_label = $export_datum['group_label']; + if ( ! array_key_exists( $group_id, $groups ) ) { + $groups[ $group_id ] = array( + 'group_label' => $group_label, + 'items' => array(), + ); + } + + $item_id = $export_datum['item_id']; + if ( ! array_key_exists( $item_id, $groups[ $group_id ]['items'] ) ) { + $groups[ $group_id ]['items'][ $item_id ] = array(); + } + + $old_item_data = $groups[ $group_id ]['items'][ $item_id ]; + $merged_item_data = array_merge( $export_datum['data'], $old_item_data ); + $groups[ $group_id ]['items'][ $item_id ] = $merged_item_data; + } + + // Then save the grouped data into the request. + delete_post_meta( $request_id, '_export_data_raw' ); + update_post_meta( $request_id, '_export_data_grouped', $groups ); + + // And now, generate the export file, cleaning up any previous file + $export_path = get_post_meta( $request_id, '_export_file_path', true ); + if ( ! empty( $export_path ) ) { + delete_post_meta( $request_id, '_export_file_path' ); + @unlink( $export_path ); + } + delete_post_meta( $request_id, '_export_file_url' ); + + // Generate the export file from the collected, grouped personal data. + do_action( 'wp_privacy_personal_data_export_file', $request_id ); + + // Clear the grouped data now that it is no longer needed. + delete_post_meta( $request_id, '_export_data_grouped' ); + + // If the destination is email, send it now. + if ( $send_as_email ) { + $mail_success = wp_privacy_send_personal_data_export_email( $request_id ); + if ( is_wp_error( $mail_success ) ) { + wp_send_json_error( $mail_success->get_error_message() ); + } + } else { + // Modify the response to include the URL of the export file so the browser can fetch it. + $export_file_url = get_post_meta( $request_id, '_export_file_url', true ); + if ( ! empty( $export_file_url ) ) { + $response['url'] = $export_file_url; + } + } + + // Update the request to completed state. + _wp_privacy_completed_request( $request_id ); + + return $response; +} + +/** + * Cleans up export files older than three days old. + * + * @since 4.9.6 + */ +function wp_privacy_delete_old_export_files() { + $upload_dir = wp_upload_dir(); + $exports_dir = trailingslashit( $upload_dir['basedir'] . '/exports' ); + $export_files = list_files( $exports_dir ); + + foreach( (array) $export_files as $export_file ) { + $file_age_in_seconds = time() - filemtime( $export_file ); + + if ( 3 * DAY_IN_SECONDS < $file_age_in_seconds ) { + @unlink( $export_file ); + } + } +} diff --git a/src/wp-admin/includes/user.php b/src/wp-admin/includes/user.php index 3d04aad0ad..5d1e963dd3 100644 --- a/src/wp-admin/includes/user.php +++ b/src/wp-admin/includes/user.php @@ -664,33 +664,6 @@ function _wp_personal_data_handle_actions() { ); } - } elseif ( isset( $_POST['export_personal_data_email_send'] ) ) { // WPCS: input var ok. - check_admin_referer( 'bulk-privacy_requests' ); - - $request_id = absint( current( array_keys( (array) wp_unslash( $_POST['export_personal_data_email_send'] ) ) ) ); // WPCS: input var ok, sanitization ok. - $result = false; - - /** - * TODO: Email the data to the user here. - */ - - if ( is_wp_error( $result ) ) { - add_settings_error( - 'export_personal_data_email_send', - 'export_personal_data_email_send', - $result->get_error_message(), - 'error' - ); - } else { - _wp_privacy_completed_request( $request_id ); - add_settings_error( - 'export_personal_data_email_send', - 'export_personal_data_email_send', - __( 'Personal data was sent to the user successfully.' ), - 'updated' - ); - } - } elseif ( isset( $_POST['action'] ) ) { $action = isset( $_POST['action'] ) ? sanitize_key( wp_unslash( $_POST['action'] ) ) : ''; // WPCS: input var ok, CSRF ok. @@ -819,6 +792,9 @@ function _wp_personal_data_export_page() { _wp_personal_data_handle_actions(); _wp_personal_data_cleanup_requests(); + // "Borrow" xfn.js for now so we don't have to create new files. + wp_enqueue_script( 'xfn' ); + $requests_table = new WP_Privacy_Data_Export_Requests_Table( array( 'plural' => 'privacy_requests', 'singular' => 'privacy_request', @@ -1361,15 +1337,18 @@ class WP_Privacy_Data_Export_Requests_Table extends WP_Privacy_Requests_Table { $request_id = $item->ID; $nonce = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id ); - $download_data_markup = '
'; - $download_data_markup .= '' . __( 'Download Personal Data' ) . '' . - '' . - ''; + $download_data_markup .= '' . __( 'Download Personal Data' ) . '' . + '' . + '' . + ''; + + $download_data_markup .= '
'; $row_actions = array( 'download_data' => $download_data_markup, @@ -1393,7 +1372,26 @@ class WP_Privacy_Data_Export_Requests_Table extends WP_Privacy_Requests_Table { esc_html_e( 'Waiting for confirmation' ); break; case 'request-confirmed': - // TODO Complete in follow on patch. + $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() ); + $exporters_count = count( $exporters ); + $request_id = $item->ID; + $nonce = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id ); + + echo '
'; + + ?> + + + + + '; break; case 'request-failed': submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item->ID . ']', false ); @@ -1461,6 +1459,8 @@ class WP_Privacy_Data_Removal_Requests_Table extends WP_Privacy_Requests_Table { '' . ''; + $remove_data_markup .= '
'; + $row_actions = array( 'remove_data' => $remove_data_markup, ); @@ -1502,6 +1502,8 @@ class WP_Privacy_Data_Removal_Requests_Table extends WP_Privacy_Requests_Table { '; + break; case 'request-failed': submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item->ID . ']', false ); diff --git a/src/wp-admin/js/xfn.js b/src/wp-admin/js/xfn.js index 7881c76159..cd43d6e1e8 100644 --- a/src/wp-admin/js/xfn.js +++ b/src/wp-admin/js/xfn.js @@ -22,7 +22,6 @@ jQuery( document ).ready(function( $ ) { }); // Privacy request action handling - jQuery( document ).ready( function( $ ) { var strings = window.privacyToolsL10n || {}; @@ -39,17 +38,98 @@ jQuery( document ).ready( function( $ ) { function appendResultsAfterRow( $requestRow, classes, summaryMessage, additionalMessages ) { clearResultsAfterRow( $requestRow ); + + var itemList = ''; if ( additionalMessages.length ) { - // TODO - render additionalMessages after the summaryMessage + $.each( additionalMessages, function( index, value ) { + itemList = itemList + '
  • ' + value + '
  • '; + } ); + itemList = ''; } $requestRow.after( function() { - return '

    ' + - summaryMessage + - '

    '; + return '' + + '
    ' + + '

    ' + summaryMessage + '

    ' + + itemList + + '
    ' + + '' + + ''; } ); } + $( '.export_personal_data a' ).click( function( event ) { + event.preventDefault(); + event.stopPropagation(); + + var $this = $( this ); + var $action = $this.parents( '.export_personal_data' ); + var $requestRow = $this.parents( 'tr' ); + var requestID = $action.data( 'request-id' ); + var nonce = $action.data( 'nonce' ); + var exportersCount = $action.data( 'exporters-count' ); + var sendAsEmail = $action.data( 'send-as-email' ) ? true : false; + + $action.blur(); + clearResultsAfterRow( $requestRow ); + + function on_export_done_success( zipUrl ) { + set_action_state( $action, 'export_personal_data_success' ); + if ( 'undefined' !== typeof zipUrl ) { + window.location = zipUrl; + } else if ( ! sendAsEmail ) { + on_export_failure( strings.noExportFile ); + } + } + + function on_export_failure( errorMessage ) { + set_action_state( $action, 'export_personal_data_failed' ); + if ( errorMessage ) { + appendResultsAfterRow( $requestRow, 'notice-error', strings.exportError, [ errorMessage ] ); + } + } + + function do_next_export( exporterIndex, pageIndex ) { + $.ajax( + { + url: window.ajaxurl, + data: { + action: 'wp-privacy-export-personal-data', + exporter: exporterIndex, + id: requestID, + page: pageIndex, + security: nonce, + sendAsEmail: sendAsEmail + }, + method: 'post' + } + ).done( function( response ) { + if ( ! response.success ) { + // e.g. invalid request ID + on_export_failure( response.data ); + return; + } + var responseData = response.data; + if ( ! responseData.done ) { + setTimeout( do_next_export( exporterIndex, pageIndex + 1 ) ); + } else { + if ( exporterIndex < exportersCount ) { + setTimeout( do_next_export( exporterIndex + 1, 1 ) ); + } else { + on_export_done_success( responseData.url ); + } + } + } ).fail( function( jqxhr, textStatus, error ) { + // e.g. Nonce failure + on_export_failure( error ); + } ); + } + + // And now, let's begin + set_action_state( $action, 'export_personal_data_processing' ); + do_next_export( 1, 1 ); + } ); + $( '.remove_personal_data a' ).click( function( event ) { event.preventDefault(); event.stopPropagation(); @@ -92,7 +172,7 @@ jQuery( document ).ready( function( $ ) { function on_erasure_failure() { set_action_state( $action, 'remove_personal_data_failed' ); - appendResultsAfterRow( $requestRow, 'notice-error', strings.anErrorOccurred, [] ); + appendResultsAfterRow( $requestRow, 'notice-error', strings.removalError, [] ); } function do_next_erasure( eraserIndex, pageIndex ) { diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 6b6e7b45af..974bc2897f 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -3352,6 +3352,7 @@ function wp_comments_personal_data_exporter( $email_address, $page = 1 ) { case 'comment_link': $value = get_comment_link( $comment->comment_ID ); + $value = '' . $value . ''; break; } diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 5685195330..991c80dc22 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -715,7 +715,9 @@ function wp_default_scripts( &$scripts ) { 'foundAndRemoved' => __( 'All of the personal data found for this user was removed.' ), 'noneRemoved' => __( 'Personal data was found for this user but was not removed.' ), 'someNotRemoved' => __( 'Personal data was found for this user but some of the personal data found was not removed.' ), - 'anErrorOccurred' => __( 'An error occurred while attempting to find and remove personal data.' ), + 'removalError' => __( 'An error occurred while attempting to find and remove personal data.' ), + 'noExportFile' => __( 'No personal data export file was generated.' ), + 'exportError' => __( 'An error occurred while attempting to export personal data.' ), ) ); diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index a18feeb328..3d3a5ef610 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -3145,7 +3145,7 @@ function wp_validate_user_request_key( $request_id, $key ) { * @since 4.9.6 * * @param int $request_id Request ID to get data about. - * @return array|false + * @return WP_User_Request|false */ function wp_get_user_request_data( $request_id ) { $request_id = absint( $request_id );