Make WordPress Core

Ticket #43602: 43602.2.diff

File 43602.2.diff, 18.2 KB (added by allendav, 5 years ago)

Oops. Forgot to include the javascript in the patch

  • src/wp-admin/includes/ajax-actions.php

     
    43414341        /**
    43424342         * Filters the array of exporter callbacks.
    43434343         *
    4344          * @since 4.9.5.
     4344         * @since 4.9.6
    43454345         *
    43464346         * @param array $args {
    43474347         *     An array of callable exporters of personal data. Default empty array.
     
    44294429         *
    44304430         * Allows the export response to be consumed by destinations in addition to Ajax.
    44314431         *
    4432          * @since 4.9.5
     4432         * @since 4.9.6
    44334433         *
    44344434         * @param array  $response        The personal data for the given exporter and page.
    44354435         * @param int    $exporter_index  The index of the exporter that provided this data.
     
    44434443
    44444444        wp_send_json_success( $response );
    44454445}
     4446
     4447/**
     4448 * Ajax handler for erasing personal data.
     4449 *
     4450 * @since 4.9.6
     4451 */
     4452function 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

     
    709709 * @access private
    710710 */
    711711function _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.
    713713                check_admin_referer( 'bulk-privacy_requests' );
    714714
    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.
    716716                $result     = _wp_privacy_resend_request( $request_id );
    717717
    718718                if ( is_wp_error( $result ) ) {
    719719                        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',
    722722                                $result->get_error_message(),
    723723                                'error'
    724724                        );
    725725                } else {
    726726                        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',
    729729                                __( 'Confirmation request re-resent successfully.' ),
    730730                                'updated'
    731731                        );
     
    908908
    909909        _wp_personal_data_handle_actions();
    910910
     911        // "Borrow" xfn.js for now so we don't have to create new files.
     912        wp_enqueue_script( 'xfn' );
     913
    911914        $requests_table = new WP_Privacy_Data_Removal_Requests_Table( array(
    912915                'plural'   => 'privacy_requests',
    913916                'singular' => 'privacy_request',
    914917        ) );
     918        $requests_table->enqueue_strings();
    915919        $requests_table->process_bulk_action();
    916920        $requests_table->prepare_items();
    917921        ?>
     
    13581362         * @return string
    13591363         */
    13601364        public function column_email( $item ) {
     1365                $exporters       = apply_filters( 'wp_privacy_personal_data_exporters', array() );
     1366                $exporters_count = count( $exporters );
     1367                $request_id      = $item['request_id'];
     1368                $nonce           = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id );
     1369
     1370                $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 ) . '">' .
     1371                        '<span class="download_personal_data_idle"><a href="#" >' . __( 'Download Personal Data' ) . '</a></span>' .
     1372                        '<span style="display:none" class="download_personal_data_processing" >' . __( 'Downloading Data...' ) . '</span>' .
     1373                        '<span style="display:none" class="download_personal_data_failed">' . __( 'Download Failed!' ) . ' <a href="#" >' . __( 'Retry' ) . '</a></span>';
     1374
    13611375                $row_actions = array(
    1362                         'download_data' => __( 'Download Personal Data' ),
     1376                        'download_data' => $download_data_markup,
    13631377                );
    13641378
    13651379                return sprintf( '%1$s %2$s', $item['email'], $this->row_actions( $row_actions ) );
     
    13831397                                // TODO Complete in follow on patch.
    13841398                                break;
    13851399                        case 'request-failed':
    1386                                 submit_button( __( 'Retry' ), 'secondary', 'export_personal_data_email_retry[' . $item['request_id'] . ']', false );
     1400                                submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item['request_id'] . ']', false );
    13871401                                break;
    13881402                        case 'request-completed':
    13891403                                echo '<a href="' . esc_url( wp_nonce_url( add_query_arg( array(
     
    14191433         */
    14201434        protected $post_type = 'user_remove_request';
    14211435
     1436        public function enqueue_strings() {
     1437                $removal_request_strings = array(
     1438                        'noDataFound'     => __( 'No personal data was found for this user.' ),
     1439                        'foundAndRemoved' => __( 'All of the personal data found for this user was removed.' ),
     1440                        'noneRemoved'     => __( 'Personal data was found for this user but was not removed.' ),
     1441                        'someNotRemoved'  => __( 'Personal data was found for this user but some of the personal data found was not removed.' ),
     1442                        'anErrorOccurred' => __( 'An error occurred while attempting to find and remove personal data.' )
     1443                );
     1444
     1445                echo '<script type="text/javascript">';
     1446                echo 'var removalRequestStrings = ' . wp_json_encode( $removal_request_strings ) . ';';
     1447                echo "</script>";
     1448        }
     1449
    14221450        /**
    14231451         * Actions column.
    14241452         *
     
    14281456         * @return string
    14291457         */
    14301458        public function column_email( $item ) {
    1431                 $row_actions = array(
    1432                         // TODO Complete in follow on patch.
    1433                         'remove_data' => __( 'Remove Personal Data' ),
    1434                 );
     1459                $row_actions = array();
    14351460
    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' );
     1461                // Allow the administrator to "force remove" the personal data even if confirmation has not yet been received
     1462                $status = get_post_status( $item['request_id'] );
     1463                if ( 'request-confirmed' !== $status ) {
     1464                        $erasers       = apply_filters( 'wp_privacy_personal_data_erasers', array() );
     1465                        $erasers_count = count( $erasers );
     1466                        $request_id    = $item['request_id'];
     1467                        $nonce         = wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id );
     1468
     1469                        $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 ) . '">' .
     1470                                '<span class="remove_personal_data_idle"><a href="#" >' . __( 'Force Remove Personal Data' ) . '</a></span>' .
     1471                                '<span style="display:none" class="remove_personal_data_processing" >' . __( 'Removing Data...' ) . '</span>' .
     1472                                '<span style="display:none" class="remove_personal_data_failed">' . __( 'Force Remove Failed!' ) . ' <a href="#" >' . __( 'Retry' ) . '</a></span>';
     1473
     1474                        $row_actions = array(
     1475                                'remove_data' => $remove_data_markup,
     1476                        );
    14401477                }
    14411478
    14421479                return sprintf( '%1$s %2$s', $item['email'], $this->row_actions( $row_actions ) );
     
    14501487         * @param array $item Item being shown.
    14511488         */
    14521489        public function column_next_steps( $item ) {
     1490                $status = get_post_status( $item['request_id'] );
     1491
     1492                switch ( $status ) {
     1493                        case 'request-pending':
     1494                                esc_html_e( 'Waiting for confirmation' );
     1495                                break;
     1496                        case 'request-confirmed':
     1497                                $erasers       = apply_filters( 'wp_privacy_personal_data_erasers', array() );
     1498                                $erasers_count = count( $erasers );
     1499                                $request_id    = $item['request_id'];
     1500                                $nonce         = wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id );
     1501
     1502                                $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 ) . '">' .
     1503                                        '<span class="remove_personal_data_idle"><a class="button" href="#" >' . __( 'Remove Personal Data' ) . '</a></span>' .
     1504                                        '<span style="display:none" class="remove_personal_data_processing button updating-message" >' . __( 'Removing Data...' ) . '</span>' .
     1505                                        '<span style="display:none" class="remove_personal_data_failed">' . __( 'Removing Data Failed!' ) . ' <a class="button" href="#" >' . __( 'Retry' ) . '</a></span>';
     1506
     1507                                echo $remove_data_markup;
     1508                                break;
     1509                        case 'request-failed':
     1510                                submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item['request_id'] . ']', false );
     1511                                break;
     1512                        case 'request-completed':
     1513                                echo '<a href="' . esc_url( wp_nonce_url( add_query_arg( array(
     1514                                        'action' => 'delete',
     1515                                        'request_id' => array( $item['request_id'] )
     1516                                ), admin_url( 'tools.php?page=remove_personal_data' ) ), 'bulk-privacy_requests' ) ) . '">' . esc_html__( 'Remove request' ) . '</a>';
     1517                                break;
     1518                }
    14531519        }
    14541520
    14551521}
  • src/wp-admin/js/xfn.js

     
    2020                $( '#link_rel' ).val( ( isMe ) ? 'me' : inputs.substr( 0,inputs.length - 1 ) );
    2121        });
    2222});
     23
     24// Privacy request action handling
     25
     26jQuery( document ).ready( function( $ ) {
     27        function set_action_state( $action, state ) {
     28                $action.children().hide();
     29                $action.children( '.' + state ).show();
     30        }
     31
     32        function clearResultsAfterRow( $requestRow ) {
     33                if ( $requestRow.next().hasClass( 'request-results' ) ) {
     34                        $requestRow.next().remove();
     35                }
     36        }
     37
     38        function appendResultsAfterRow( $requestRow, classes, summaryMessage, additionalMessages ) {
     39                clearResultsAfterRow( $requestRow );
     40                if ( additionalMessages.length ) {
     41                        // TODO - render additionalMessages after the summaryMessage
     42                }
     43
     44                $requestRow.after( function() {
     45                        return '<tr class="request-results"><td colspan="5"><div class="notice inline notice-alt ' + classes + '"><p>' +
     46                                summaryMessage +
     47                                '</p></div></td></tr>';
     48                } );
     49        }
     50
     51        $( '.remove_personal_data a' ).click( function( event ) {
     52                event.preventDefault();
     53                event.stopPropagation();
     54
     55                var $this         = $( this );
     56                var $action       = $this.parents( '.remove_personal_data' );
     57                var $requestRow   = $this.parents( 'tr' );
     58                var requestID     = $action.data( 'request-id' );
     59                var nonce         = $action.data( 'nonce' );
     60                var erasersCount  = $action.data( 'erasers-count' );
     61
     62                var removedCount  = 0;
     63                var retainedCount = 0;
     64                var messages      = [];
     65
     66                $action.blur();
     67                clearResultsAfterRow( $requestRow );
     68
     69                function on_erasure_done_success() {
     70                        set_action_state( $action, 'remove_personal_data_idle' );
     71                        var summaryMessage = removalRequestStrings.noDataFound;
     72                        var classes = 'notice-success';
     73                        if ( 0 == removedCount ) {
     74                                if ( 0 == retainedCount ) {
     75                                        summaryMessage = removalRequestStrings.noDataFound;
     76                                } else {
     77                                        summaryMessage = removalRequestStrings.noneRemoved;
     78                                        classes = 'notice-warning';
     79                                }
     80                        } else {
     81                                if ( 0 == retainedCount ) {
     82                                        summaryMessage = removalRequestStrings.foundAndRemoved;
     83                                } else {
     84                                        summaryMessage = removalRequestStrings.someNotRemoved;
     85                                        classes = 'notice-warning';
     86                                }
     87                        }
     88                        appendResultsAfterRow( $requestRow, 'notice-success', summaryMessage, [] );
     89                }
     90
     91                function on_erasure_failure( textStatus, error ) {
     92                        set_action_state( $action, 'remove_personal_data_failed' );
     93                        appendResultsAfterRow( $requestRow, 'notice-error', removalRequestStrings.anErrorOccurred, [] );
     94                }
     95
     96                function do_next_erasure( eraserIndex, pageIndex ) {
     97                        $.ajax( {
     98                                url: ajaxurl,
     99                                data: {
     100                                        action: 'wp-privacy-erase-personal-data',
     101                                        eraser: eraserIndex,
     102                                        id: requestID,
     103                                        page: pageIndex,
     104                                        security: nonce,
     105                                },
     106                                method: 'post'
     107                        } ).done( function( response ) {
     108                                if ( ! response.success ) {
     109                                        on_erasure_failure( 'error', response.data );
     110                                        return;
     111                                }
     112                                var responseData = response.data;
     113                                if ( responseData.num_items_removed ) {
     114                                        removedCount += responseData.num_items_removed;
     115                                }
     116                                if ( responseData.num_items_retained ) {
     117                                        retainedCount += responseData.num_items_removed;
     118                                }
     119                                if ( responseData.messages ) {
     120                                        messages = messages.concat( responseData.messages );
     121                                }
     122                                if ( ! responseData.done ) {
     123                                        setTimeout( do_next_erasure( eraserIndex, pageIndex + 1 ) );
     124                                } else {
     125                                        if ( eraserIndex < erasersCount ) {
     126                                                setTimeout( do_next_erasure( eraserIndex + 1, 1 ) );
     127                                        } else {
     128                                                on_erasure_done_success();
     129                                        }
     130                                }
     131                        } ).fail( function( jqxhr, textStatus, error ) {
     132                                on_erasure_failure( textStatus, error );
     133                        } );
     134                }
     135
     136                // And now, let's begin
     137                set_action_state( $action, 'remove_personal_data_processing' );
     138
     139                do_next_erasure( 1, 1 );
     140        } )
     141} );