Ticket #43602: 43602.3.diff
File 43602.3.diff, 18.4 KB (added by , 5 years ago) |
---|
-
src/wp-admin/includes/ajax-actions.php
4341 4341 /** 4342 4342 * Filters the array of exporter callbacks. 4343 4343 * 4344 * @since 4.9. 5.4344 * @since 4.9.6 4345 4345 * 4346 4346 * @param array $args { 4347 4347 * An array of callable exporters of personal data. Default empty array. … … 4429 4429 * 4430 4430 * Allows the export response to be consumed by destinations in addition to Ajax. 4431 4431 * 4432 * @since 4.9. 54432 * @since 4.9.6 4433 4433 * 4434 4434 * @param array $response The personal data for the given exporter and page. 4435 4435 * @param int $exporter_index The index of the exporter that provided this data. … … 4443 4443 4444 4444 wp_send_json_success( $response ); 4445 4445 } 4446 4447 /** 4448 * Ajax handler for erasing personal data. 4449 * 4450 * @since 4.9.6 4451 */ 4452 function wp_ajax_wp_privacy_erase_personal_data() { 4453 $request_id = sanitize_text_field( $_POST['id'] ); 4454 check_ajax_referer( 'wp-privacy-erase-personal-data-' . $request_id, 'security' ); 4455 4456 // Find the request CPT 4457 $request = get_post( $request_id ); 4458 if ( 'user_remove_request' !== $request->post_type ) { 4459 wp_send_json_error( __( 'Error: Invalid request ID.' ) ); 4460 } 4461 4462 $email_address = get_post_meta( $request_id, '_user_email', true ); 4463 if ( function_exists( 'mb_strtolower' ) ) { 4464 $email_address = trim( mb_strtolower( $email_address ) ); 4465 } else { 4466 $email_address = trim( strtolower( $email_address ) ); 4467 } 4468 4469 if ( ! is_email( $email_address ) ) { 4470 wp_send_json_error( __( 'Error: Invalid email address in request.' ) ); 4471 } 4472 4473 $eraser_index = (int) $_POST['eraser']; 4474 $page = (int) $_POST['page']; 4475 4476 /** 4477 * Filters the array of personal data eraser callbacks. 4478 * 4479 * @since 4.9.6 4480 * 4481 * @param array $args { 4482 * An array of callable erasers of personal data. Default empty array. 4483 * [ 4484 * callback string Callable eraser that accepts an email address and 4485 * a page and returns an array with the number of items 4486 * removed, the number of items retained and any messages 4487 * from the eraser, as well as if additional pages are 4488 * available 4489 * exporter_friendly_name string Translated user facing friendly name for the eraser 4490 * ] 4491 * } 4492 */ 4493 $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() ); 4494 4495 // Do we have any registered erasers? 4496 if ( 0 < count( $erasers ) ) { 4497 if ( $eraser_index < 1 ) { 4498 wp_send_json_error( __( 'Error: Eraser index cannot be less than one.' ) ); 4499 } 4500 4501 if ( $eraser_index > count( $erasers ) ) { 4502 wp_send_json_error( __( 'Error: Eraser index is out of range.' ) ); 4503 } 4504 4505 if ( $page < 1 ) { 4506 wp_send_json_error( __( 'Error: Page index cannot be less than one.' ) ); 4507 } 4508 4509 $index = $eraser_index - 1; // Convert to zero based for eraser index 4510 $eraser = $erasers[ $index ]; 4511 if ( ! is_array( $eraser ) ) { 4512 wp_send_json_error( 4513 sprintf( 4514 __( 'Error: Expected an array describing the eraser at index %d.' ), 4515 $eraser_index 4516 ) 4517 ); 4518 } 4519 if ( ! array_key_exists( 'callback', $eraser ) ) { 4520 wp_send_json_error( 4521 sprintf( 4522 __( 'Error: Eraser array at index %d does not include a callback.' ), 4523 $eraser_index 4524 ) 4525 ); 4526 } 4527 if ( ! is_callable( $eraser['callback'] ) ) { 4528 wp_send_json_error( 4529 sprintf( 4530 __( 'Error: Eraser callback at index %d is not a valid callback.' ), 4531 $eraser_index 4532 ) 4533 ); 4534 } 4535 if ( ! array_key_exists( 'eraser_friendly_name', $eraser ) ) { 4536 wp_send_json_error( 4537 sprintf( 4538 __( 'Error: Eraser array at index %d does not include a friendly name.' ), 4539 $eraser_index 4540 ) 4541 ); 4542 } 4543 4544 $callback = $erasers[ $index ]['callback']; 4545 $eraser_friendly_name = $erasers[ $index ]['eraser_friendly_name']; 4546 4547 $response = call_user_func( $callback, $email_address, $page ); 4548 if ( is_wp_error( $response ) ) { 4549 wp_send_json_error( $response ); 4550 } 4551 4552 if ( ! is_array( $response ) ) { 4553 wp_send_json_error( 4554 sprintf( 4555 __( 'Error: Did not receive array from %s eraser (index %d).' ), 4556 $eraser_friendly_name, 4557 $eraser_index 4558 ) 4559 ); 4560 } 4561 if ( ! array_key_exists( 'num_items_removed', $response ) ) { 4562 wp_send_json_error( 4563 sprintf( 4564 __( 'Error: Expected num_items_removed key in response array from %s eraser (index %d).' ), 4565 $eraser_friendly_name, 4566 $eraser_index 4567 ) 4568 ); 4569 } 4570 if ( ! array_key_exists( 'num_items_retained', $response ) ) { 4571 wp_send_json_error( 4572 sprintf( 4573 __( 'Error: Expected num_items_retained key in response array from %s eraser (index %d).' ), 4574 $eraser_friendly_name, 4575 $eraser_index 4576 ) 4577 ); 4578 } 4579 if ( ! array_key_exists( 'messages', $response ) ) { 4580 wp_send_json_error( 4581 sprintf( 4582 __( 'Error: Expected messages key in response array from %s eraser (index %d).' ), 4583 $eraser_friendly_name, 4584 $eraser_index 4585 ) 4586 ); 4587 } 4588 if ( ! is_array( $response['messages'] ) ) { 4589 wp_send_json_error( 4590 sprintf( 4591 __( 'Error: Expected messages key to reference an array in response array from %s eraser (index %d).' ), 4592 $eraser_friendly_name, 4593 $eraser_index 4594 ) 4595 ); 4596 } 4597 if ( ! array_key_exists( 'done', $response ) ) { 4598 wp_send_json_error( 4599 sprintf( 4600 __( 'Error: Expected done flag in response array from %s eraser (index %d).' ), 4601 $eraser_friendly_name, 4602 $eraser_index 4603 ) 4604 ); 4605 } 4606 } else { 4607 // No erasers, so we're done 4608 $response = array( 4609 'num_items_removed' => 0, 4610 'num_items_retained' => 0, 4611 'messages' => array(), 4612 'done' => true, 4613 ); 4614 } 4615 4616 /** 4617 * Filters a page of personal data eraser data. 4618 * 4619 * Allows the erasure response to be consumed by destinations in addition to Ajax. 4620 * 4621 * @since 4.9.6 4622 * 4623 * @param array $response The personal data for the given exporter and page. 4624 * @param int $exporter_index The index of the exporter that provided this data. 4625 * @param string $email_address The email address associated with this personal data. 4626 * @param int $page The zero-based page for this response. 4627 * @param int $request_id The privacy request post ID associated with this request. 4628 */ 4629 $response = apply_filters( 'wp_privacy_personal_data_erasure_page', $response, $eraser_index, $email_address, $page, $request_id ); 4630 if ( is_wp_error( $response ) ) { 4631 wp_send_json_error( $response ); 4632 } 4633 4634 wp_send_json_success( $response ); 4635 } -
src/wp-admin/includes/user.php
709 709 * @access private 710 710 */ 711 711 function _wp_personal_data_handle_actions() { 712 if ( isset( $_POST[' export_personal_data_email_retry'] ) ) { // WPCS: input var ok.712 if ( isset( $_POST['privacy_action_email_retry'] ) ) { // WPCS: input var ok. 713 713 check_admin_referer( 'bulk-privacy_requests' ); 714 714 715 $request_id = absint( current( array_keys( (array) wp_unslash( $_POST[' export_personal_data_email_retry'] ) ) ) ); // WPCS: input var ok, sanitization ok.715 $request_id = absint( current( array_keys( (array) wp_unslash( $_POST['privacy_action_email_retry'] ) ) ) ); // WPCS: input var ok, sanitization ok. 716 716 $result = _wp_privacy_resend_request( $request_id ); 717 717 718 718 if ( is_wp_error( $result ) ) { 719 719 add_settings_error( 720 ' export_personal_data_email_retry',721 ' export_personal_data_email_retry',720 'privacy_action_email_retry', 721 'privacy_action_email_retry', 722 722 $result->get_error_message(), 723 723 'error' 724 724 ); 725 725 } else { 726 726 add_settings_error( 727 ' export_personal_data_email_retry',728 ' export_personal_data_email_retry',727 'privacy_action_email_retry', 728 'privacy_action_email_retry', 729 729 __( 'Confirmation request re-resent successfully.' ), 730 730 'updated' 731 731 ); … … 908 908 909 909 _wp_personal_data_handle_actions(); 910 910 911 // "Borrow" xfn.js for now so we don't have to create new files. 912 wp_enqueue_script( 'xfn' ); 913 911 914 $requests_table = new WP_Privacy_Data_Removal_Requests_Table( array( 912 915 'plural' => 'privacy_requests', 913 916 'singular' => 'privacy_request', 914 917 ) ); 918 915 919 $requests_table->process_bulk_action(); 916 920 $requests_table->prepare_items(); 921 917 922 ?> 918 923 <div class="wrap nosubsub"> 919 924 <h1><?php esc_html_e( 'Remove Personal Data' ); ?></h1> … … 1358 1363 * @return string 1359 1364 */ 1360 1365 public function column_email( $item ) { 1366 $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() ); 1367 $exporters_count = count( $exporters ); 1368 $request_id = $item['request_id']; 1369 $nonce = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id ); 1370 1371 $download_data_markup = '<div class="download_personal_data" data-exporters-count="' . esc_attr( $exporters_count ) . '" data-request-id="' . esc_attr( $request_id ) . '" data-nonce="' . esc_attr( $nonce ) . '">' . 1372 '<span class="download_personal_data_idle"><a href="#" >' . __( 'Download Personal Data' ) . '</a></span>' . 1373 '<span style="display:none" class="download_personal_data_processing" >' . __( 'Downloading Data...' ) . '</span>' . 1374 '<span style="display:none" class="download_personal_data_failed">' . __( 'Download Failed!' ) . ' <a href="#" >' . __( 'Retry' ) . '</a></span>'; 1375 1361 1376 $row_actions = array( 1362 'download_data' => __( 'Download Personal Data' ),1377 'download_data' => $download_data_markup, 1363 1378 ); 1364 1379 1365 1380 return sprintf( '%1$s %2$s', $item['email'], $this->row_actions( $row_actions ) ); … … 1383 1398 // TODO Complete in follow on patch. 1384 1399 break; 1385 1400 case 'request-failed': 1386 submit_button( __( 'Retry' ), 'secondary', ' export_personal_data_email_retry[' . $item['request_id'] . ']', false );1401 submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item['request_id'] . ']', false ); 1387 1402 break; 1388 1403 case 'request-completed': 1389 1404 echo '<a href="' . esc_url( wp_nonce_url( add_query_arg( array( … … 1428 1443 * @return string 1429 1444 */ 1430 1445 public function column_email( $item ) { 1431 $row_actions = array( 1432 // TODO Complete in follow on patch. 1433 'remove_data' => __( 'Remove Personal Data' ), 1434 ); 1446 $row_actions = array(); 1435 1447 1436 // If we have a user ID, include a delete user action. 1437 if ( ! empty( $item['user_id'] ) ) { 1438 // TODO Complete in follow on patch. 1439 $row_actions['delete_user'] = __( 'Delete User' ); 1448 // Allow the administrator to "force remove" the personal data even if confirmation has not yet been received 1449 $status = get_post_status( $item['request_id'] ); 1450 if ( 'request-confirmed' !== $status ) { 1451 $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() ); 1452 $erasers_count = count( $erasers ); 1453 $request_id = $item['request_id']; 1454 $nonce = wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id ); 1455 1456 $remove_data_markup = '<div class="remove_personal_data force_remove_personal_data" data-erasers-count="' . esc_attr( $erasers_count ) . '" data-request-id="' . esc_attr( $request_id ) . '" data-nonce="' . esc_attr( $nonce ) . '">' . 1457 '<span class="remove_personal_data_idle"><a href="#" >' . __( 'Force Remove Personal Data' ) . '</a></span>' . 1458 '<span style="display:none" class="remove_personal_data_processing" >' . __( 'Removing Data...' ) . '</span>' . 1459 '<span style="display:none" class="remove_personal_data_failed">' . __( 'Force Remove Failed!' ) . ' <a href="#" >' . __( 'Retry' ) . '</a></span>'; 1460 1461 $row_actions = array( 1462 'remove_data' => $remove_data_markup, 1463 ); 1440 1464 } 1441 1465 1442 1466 return sprintf( '%1$s %2$s', $item['email'], $this->row_actions( $row_actions ) ); … … 1450 1474 * @param array $item Item being shown. 1451 1475 */ 1452 1476 public function column_next_steps( $item ) { 1477 $status = get_post_status( $item['request_id'] ); 1478 1479 switch ( $status ) { 1480 case 'request-pending': 1481 esc_html_e( 'Waiting for confirmation' ); 1482 break; 1483 case 'request-confirmed': 1484 $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() ); 1485 $erasers_count = count( $erasers ); 1486 $request_id = $item['request_id']; 1487 $nonce = wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id ); 1488 1489 $remove_data_markup = '<div class="remove_personal_data" data-force-erase="1" data-erasers-count="' . esc_attr( $erasers_count ) . '" data-request-id="' . esc_attr( $request_id ) . '" data-nonce="' . esc_attr( $nonce ) . '">' . 1490 '<span class="remove_personal_data_idle"><a class="button" href="#" >' . __( 'Remove Personal Data' ) . '</a></span>' . 1491 '<span style="display:none" class="remove_personal_data_processing button updating-message" >' . __( 'Removing Data...' ) . '</span>' . 1492 '<span style="display:none" class="remove_personal_data_failed">' . __( 'Removing Data Failed!' ) . ' <a class="button" href="#" >' . __( 'Retry' ) . '</a></span>'; 1493 1494 echo $remove_data_markup; 1495 break; 1496 case 'request-failed': 1497 submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item['request_id'] . ']', false ); 1498 break; 1499 case 'request-completed': 1500 echo '<a href="' . esc_url( wp_nonce_url( add_query_arg( array( 1501 'action' => 'delete', 1502 'request_id' => array( $item['request_id'] ) 1503 ), admin_url( 'tools.php?page=remove_personal_data' ) ), 'bulk-privacy_requests' ) ) . '">' . esc_html__( 'Remove request' ) . '</a>'; 1504 break; 1505 } 1453 1506 } 1454 1507 1455 1508 } -
src/wp-admin/js/xfn.js
20 20 $( '#link_rel' ).val( ( isMe ) ? 'me' : inputs.substr( 0,inputs.length - 1 ) ); 21 21 }); 22 22 }); 23 24 // Privacy request action handling 25 26 jQuery( document ).ready( function( $ ) { 27 var strings = window.privacyToolsL10n || {}; 28 29 function set_action_state( $action, state ) { 30 $action.children().hide(); 31 $action.children( '.' + state ).show(); 32 } 33 34 function clearResultsAfterRow( $requestRow ) { 35 if ( $requestRow.next().hasClass( 'request-results' ) ) { 36 $requestRow.next().remove(); 37 } 38 } 39 40 function appendResultsAfterRow( $requestRow, classes, summaryMessage, additionalMessages ) { 41 clearResultsAfterRow( $requestRow ); 42 if ( additionalMessages.length ) { 43 // TODO - render additionalMessages after the summaryMessage 44 } 45 46 $requestRow.after( function() { 47 return '<tr class="request-results"><td colspan="5"><div class="notice inline notice-alt ' + classes + '"><p>' + 48 summaryMessage + 49 '</p></div></td></tr>'; 50 } ); 51 } 52 53 $( '.remove_personal_data a' ).click( function( event ) { 54 event.preventDefault(); 55 event.stopPropagation(); 56 57 var $this = $( this ); 58 var $action = $this.parents( '.remove_personal_data' ); 59 var $requestRow = $this.parents( 'tr' ); 60 var requestID = $action.data( 'request-id' ); 61 var nonce = $action.data( 'nonce' ); 62 var erasersCount = $action.data( 'erasers-count' ); 63 64 var removedCount = 0; 65 var retainedCount = 0; 66 var messages = []; 67 68 $action.blur(); 69 clearResultsAfterRow( $requestRow ); 70 71 function on_erasure_done_success() { 72 set_action_state( $action, 'remove_personal_data_idle' ); 73 var summaryMessage = strings.noDataFound; 74 var classes = 'notice-success'; 75 if ( 0 == removedCount ) { 76 if ( 0 == retainedCount ) { 77 summaryMessage = strings.noDataFound; 78 } else { 79 summaryMessage = strings.noneRemoved; 80 classes = 'notice-warning'; 81 } 82 } else { 83 if ( 0 == retainedCount ) { 84 summaryMessage = strings.foundAndRemoved; 85 } else { 86 summaryMessage = strings.someNotRemoved; 87 classes = 'notice-warning'; 88 } 89 } 90 appendResultsAfterRow( $requestRow, 'notice-success', summaryMessage, [] ); 91 } 92 93 function on_erasure_failure( textStatus, error ) { 94 set_action_state( $action, 'remove_personal_data_failed' ); 95 appendResultsAfterRow( $requestRow, 'notice-error', strings.anErrorOccurred, [] ); 96 } 97 98 function do_next_erasure( eraserIndex, pageIndex ) { 99 $.ajax( { 100 url: ajaxurl, 101 data: { 102 action: 'wp-privacy-erase-personal-data', 103 eraser: eraserIndex, 104 id: requestID, 105 page: pageIndex, 106 security: nonce, 107 }, 108 method: 'post' 109 } ).done( function( response ) { 110 if ( ! response.success ) { 111 on_erasure_failure( 'error', response.data ); 112 return; 113 } 114 var responseData = response.data; 115 if ( responseData.num_items_removed ) { 116 removedCount += responseData.num_items_removed; 117 } 118 if ( responseData.num_items_retained ) { 119 retainedCount += responseData.num_items_removed; 120 } 121 if ( responseData.messages ) { 122 messages = messages.concat( responseData.messages ); 123 } 124 if ( ! responseData.done ) { 125 setTimeout( do_next_erasure( eraserIndex, pageIndex + 1 ) ); 126 } else { 127 if ( eraserIndex < erasersCount ) { 128 setTimeout( do_next_erasure( eraserIndex + 1, 1 ) ); 129 } else { 130 on_erasure_done_success(); 131 } 132 } 133 } ).fail( function( jqxhr, textStatus, error ) { 134 on_erasure_failure( textStatus, error ); 135 } ); 136 } 137 138 // And now, let's begin 139 set_action_state( $action, 'remove_personal_data_processing' ); 140 141 do_next_erasure( 1, 1 ); 142 } ) 143 } ); -
src/wp-includes/script-loader.php
709 709 ); 710 710 711 711 $scripts->add( 'xfn', "/wp-admin/js/xfn$suffix.js", array( 'jquery' ), false, 1 ); 712 did_action( 'init' ) && $scripts->localize( 713 'xfn', 'privacyToolsL10n', array( 714 'noDataFound' => __( 'No personal data was found for this user.' ), 715 'foundAndRemoved' => __( 'All of the personal data found for this user was removed.' ), 716 'noneRemoved' => __( 'Personal data was found for this user but was not removed.' ), 717 'someNotRemoved' => __( 'Personal data was found for this user but some of the personal data found was not removed.' ), 718 'anErrorOccurred' => __( 'An error occurred while attempting to find and remove personal data.' ), 719 ) 720 ); 712 721 713 722 $scripts->add( 'postbox', "/wp-admin/js/postbox$suffix.js", array( 'jquery-ui-sortable' ), false, 1 ); 714 723 did_action( 'init' ) && $scripts->localize(