Make WordPress Core

Changeset 42967


Ignore:
Timestamp:
04/10/2018 06:01:20 PM (7 years ago)
Author:
azaozz
Message:

Privacy: add new wp-admin screens for exporting and removing of personal data.

Props @melchoyce, @mikejolley, @allendav, @xkon.
See #43481.

Location:
trunk/src
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/css/forms.css

    r42864 r42967  
    14041404    }
    14051405}
     1406
     1407
     1408/* Privacy */
     1409
     1410.privacy_requests .column-email {
     1411    width: 40%;
     1412}
     1413.privacy_requests .column-type {
     1414    text-align: center;
     1415}
     1416.privacy_requests thead td:first-child,
     1417.privacy_requests tfoot td:first-child {
     1418    border-left: 4px solid #fff;
     1419}
     1420.privacy_requests tbody th {
     1421    border-left: 4px solid #fff;
     1422    background: #fff;
     1423    box-shadow: inset 0 -1px 0 rgba(0,0,0,0.1);
     1424}
     1425.privacy_requests tbody td {
     1426    background: #fff;
     1427    box-shadow: inset 0 -1px 0 rgba(0,0,0,0.1);
     1428}
     1429.privacy_requests .status-request-confirmed th,
     1430.privacy_requests .status-request-confirmed td {
     1431    background-color: #f7fcfe;
     1432    border-left-color: #00a0d2;
     1433}
     1434.privacy_requests .status-request-failed th,
     1435.privacy_requests .status-request-failed td {
     1436    background-color: #fef7f1;
     1437    border-left-color: #d64d21;
     1438}
     1439.status-label {
     1440    font-weight: bold;
     1441}
     1442.status-label.status-request-pending {
     1443    font-weight: normal;
     1444    font-style: italic;
     1445    color: #6c7781;
     1446}
     1447.status-label.status-request-failed {
     1448    color: #aa0000;
     1449    font-weight: bold;
     1450}
     1451.wp-privacy-request-form {
     1452    clear: both;
     1453}
     1454.wp-privacy-request-form-field {
     1455    margin: 1.5em 0;
     1456}
     1457.wp-privacy-request-form label {
     1458    font-weight: bold;
     1459    line-height: 1.5;
     1460    padding-bottom: .5em;
     1461    display: block;
     1462}
     1463.wp-privacy-request-form input {
     1464    line-height: 1.5;
     1465    margin: 0;
     1466}
     1467.email-personal-data::before {
     1468    display: inline-block;
     1469    font: normal 20px/1 dashicons;
     1470    margin: 3px 5px 0 -2px;
     1471    speak: none;
     1472    -webkit-font-smoothing: antialiased;
     1473    -moz-osx-font-smoothing: grayscale;
     1474    vertical-align: top;
     1475}
     1476.email-personal-data--sending::before {
     1477    color: #f56e28;
     1478    content: "\f463";
     1479    -webkit-animation: rotation 2s infinite linear;
     1480    animation: rotation 2s infinite linear;
     1481}
     1482.email-personal-data--sent::before {
     1483    color: #79ba49;
     1484    content: "\f147";
     1485}
     1486@-webkit-keyframes rotation {
     1487    0% {
     1488        -webkit-transform: rotate(0deg);
     1489        transform: rotate(0deg);
     1490    }
     1491    100% {
     1492        -webkit-transform: rotate(359deg);
     1493        transform: rotate(359deg);
     1494    }
     1495}
     1496@keyframes rotation {
     1497    0% {
     1498        -webkit-transform: rotate(0deg);
     1499        transform: rotate(0deg);
     1500    }
     1501    100% {
     1502        -webkit-transform: rotate(359deg);
     1503        transform: rotate(359deg);
     1504    }
     1505}
  • trunk/src/wp-admin/includes/admin-filters.php

    r42343 r42967  
    4545add_action( 'admin_head', 'wp_site_icon' );
    4646add_action( 'admin_head', '_ipad_meta' );
     47
     48// Privacy tools
     49add_action( 'account_action_failed', '_wp_privacy_account_request_failed' );
     50add_action( 'admin_menu', '_wp_privacy_hook_requests_page' );
    4751
    4852// Prerendering.
  • trunk/src/wp-admin/includes/user.php

    r42875 r42967  
    580580    );
    581581}
     582
     583/**
     584 * Get action description from the name.
     585 *
     586 * @since 5.0.0
     587 * @access private
     588 *
     589 * @return string
     590 */
     591function _wp_privacy_action_description( $request_type ) {
     592    switch ( $request_type ) {
     593        case 'user_export_request':
     594            return __( 'Export Personal Data' );
     595        case 'user_remove_request':
     596            return __( 'Remove Personal Data' );
     597    }
     598}
     599
     600/**
     601 * Log a request and send to the user.
     602 *
     603 * @since 5.0.0
     604 * @access private
     605 *
     606 * @param string $email_address Email address sending the request to.
     607 * @param string $action Action being requested.
     608 * @param string $description Description of request.
     609 * @return bool|WP_Error depending on success.
     610 */
     611function _wp_privacy_create_request( $email_address, $action, $description ) {
     612    $user_id = 0;
     613    $user    = get_user_by( 'email', $email_address );
     614
     615    if ( $user ) {
     616        $user_id = $user->ID;
     617    }
     618
     619    $privacy_request_id = wp_insert_post( array(
     620        'post_author'   => $user_id,
     621        'post_status'   => 'request-pending',
     622        'post_type'     => $action,
     623        'post_date'     => current_time( 'mysql', false ),
     624        'post_date_gmt' => current_time( 'mysql', true ),
     625    ), true );
     626
     627    if ( is_wp_error( $privacy_request_id ) ) {
     628        return $privacy_request_id;
     629    }
     630
     631    update_post_meta( $privacy_request_id, '_user_email', $email_address );
     632    update_post_meta( $privacy_request_id, '_action_name', $action );
     633    update_post_meta( $privacy_request_id, '_confirmed_timestamp', false );
     634
     635    return wp_send_account_verification_key( $email_address, $action, $description, array(
     636        'privacy_request_id' => $privacy_request_id,
     637    ) );
     638}
     639
     640/**
     641 * Resend an existing request and return the result.
     642 *
     643 * @since 5.0.0
     644 * @access private
     645 *
     646 * @param int $privacy_request_id Request ID.
     647 * @return bool|WP_Error
     648 */
     649function _wp_privacy_resend_request( $privacy_request_id ) {
     650    $privacy_request_id = absint( $privacy_request_id );
     651    $privacy_request    = get_post( $privacy_request_id );
     652
     653    if ( ! $privacy_request || ! in_array( $privacy_request->post_type, _wp_privacy_action_request_types(), true ) ) {
     654        return new WP_Error( 'privacy_request_error', __( 'Invalid request.' ) );
     655    }
     656
     657    $email_address = get_post_meta( $privacy_request_id, '_user_email', true );
     658    $action        = get_post_meta( $privacy_request_id, '_action_name', true );
     659    $description   = _wp_privacy_action_description( $action );
     660    $result        = wp_send_account_verification_key( $email_address, $action, $description, array(
     661        'privacy_request_id' => $privacy_request_id,
     662    ) );
     663
     664    if ( is_wp_error( $result ) ) {
     665        return $result;
     666    } elseif ( ! $result ) {
     667        return new WP_Error( 'privacy_request_error', __( 'Unable to initiate confirmation request.' ) );
     668    }
     669
     670    wp_update_post( array(
     671        'ID'            => $privacy_request_id,
     672        'post_status'   => 'request-pending',
     673        'post_date'     => current_time( 'mysql', false ),
     674        'post_date_gmt' => current_time( 'mysql', true ),
     675    ) );
     676
     677    return true;
     678}
     679
     680/**
     681 * Marks a request as completed by the admin and logs the datetime.
     682 *
     683 * @since 5.0.0
     684 * @access private
     685 *
     686 * @param int $privacy_request_id Request ID.
     687 * @return bool|WP_Error
     688 */
     689function _wp_privacy_completed_request( $privacy_request_id ) {
     690    $privacy_request_id = absint( $privacy_request_id );
     691    $privacy_request    = get_post( $privacy_request_id );
     692
     693    if ( ! $privacy_request || ! in_array( $privacy_request->post_type, _wp_privacy_action_request_types(), true ) ) {
     694        return new WP_Error( 'privacy_request_error', __( 'Invalid request.' ) );
     695    }
     696
     697    wp_update_post( array(
     698        'ID'          => $privacy_request_id,
     699        'post_status' => 'request-completed',
     700    ) );
     701
     702    update_post_meta( $privacy_request_id, '_completed_timestamp', time() );
     703}
     704
     705/**
     706 * Handle list table actions.
     707 *
     708 * @since 5.0.0
     709 * @access private
     710 */
     711function _wp_personal_data_handle_actions() {
     712    if ( isset( $_POST['export_personal_data_email_retry'] ) ) { // WPCS: input var ok.
     713        check_admin_referer( 'bulk-privacy_requests' );
     714
     715        $request_id = absint( current( array_keys( (array) wp_unslash( $_POST['export_personal_data_email_retry'] ) ) ) ); // WPCS: input var ok, sanitization ok.
     716        $result     = _wp_privacy_resend_request( $request_id );
     717
     718        if ( is_wp_error( $result ) ) {
     719            add_settings_error(
     720                'export_personal_data_email_retry',
     721                'export_personal_data_email_retry',
     722                $result->get_error_message(),
     723                'error'
     724            );
     725        } else {
     726            add_settings_error(
     727                'export_personal_data_email_retry',
     728                'export_personal_data_email_retry',
     729                __( 'Confirmation request re-resent successfully.' ),
     730                'updated'
     731            );
     732        }
     733
     734    } elseif ( isset( $_POST['export_personal_data_email_send'] ) ) { // WPCS: input var ok.
     735        check_admin_referer( 'bulk-privacy_requests' );
     736
     737        $request_id = absint( current( array_keys( (array) wp_unslash( $_POST['export_personal_data_email_send'] ) ) ) ); // WPCS: input var ok, sanitization ok.
     738        $result     = false;
     739
     740        /**
     741         * TODO: Email the data to the user here.
     742         */
     743
     744        if ( is_wp_error( $result ) ) {
     745            add_settings_error(
     746                'export_personal_data_email_send',
     747                'export_personal_data_email_send',
     748                $result->get_error_message(),
     749                'error'
     750            );
     751        } else {
     752            _wp_privacy_completed_request( $request_id );
     753            add_settings_error(
     754                'export_personal_data_email_send',
     755                'export_personal_data_email_send',
     756                __( 'Personal data was sent to the user successfully.' ),
     757                'updated'
     758            );
     759        }
     760
     761    } elseif ( isset( $_POST['action'] ) ) {
     762        $action = isset( $_POST['action'] ) ? sanitize_key( wp_unslash( $_POST['action'] ) ) : ''; // WPCS: input var ok, CSRF ok.
     763
     764        switch ( $action ) {
     765            case 'add_export_personal_data_request':
     766            case 'add_remove_personal_data_request':
     767                check_admin_referer( 'personal-data-request' );
     768
     769                if ( ! isset( $_POST['type_of_action'], $_POST['username_or_email_to_export'] ) ) { // WPCS: input var ok.
     770                    add_settings_error(
     771                        'action_type',
     772                        'action_type',
     773                        __( 'Invalid action.' ),
     774                        'error'
     775                    );
     776                }
     777                $action_type               = sanitize_text_field( wp_unslash( $_POST['type_of_action'] ) ); // WPCS: input var ok.
     778                $username_or_email_address = sanitize_text_field( wp_unslash( $_POST['username_or_email_to_export'] ) ); // WPCS: input var ok.
     779                $email_address             = '';
     780
     781                if ( ! in_array( $action_type, _wp_privacy_action_request_types(), true ) ) {
     782                    add_settings_error(
     783                        'action_type',
     784                        'action_type',
     785                        __( 'Invalid action.' ),
     786                        'error'
     787                    );
     788                }
     789
     790                if ( ! is_email( $username_or_email_address ) ) {
     791                    $user = get_user_by( 'login', $username_or_email_address );
     792                    if ( ! $user instanceof WP_User ) {
     793                        add_settings_error(
     794                            'username_or_email_to_export',
     795                            'username_or_email_to_export',
     796                            __( 'Unable to add export request. A valid email address or username must be supplied.' ),
     797                            'error'
     798                        );
     799                    } else {
     800                        $email_address = $user->user_email;
     801                    }
     802                } else {
     803                    $email_address = $username_or_email_address;
     804                }
     805
     806                if ( ! empty( $email_address ) ) {
     807                    $result = _wp_privacy_create_request( $email_address, $action_type, _wp_privacy_action_description( $action_type ) );
     808
     809                    if ( is_wp_error( $result ) ) {
     810                        add_settings_error(
     811                            'username_or_email_to_export',
     812                            'username_or_email_to_export',
     813                            $result->get_error_message(),
     814                            'error'
     815                        );
     816                    } elseif ( ! $result ) {
     817                        add_settings_error(
     818                            'username_or_email_to_export',
     819                            'username_or_email_to_export',
     820                            __( 'Unable to initiate confirmation request.' ),
     821                            'error'
     822                        );
     823                    } else {
     824                        add_settings_error(
     825                            'username_or_email_to_export',
     826                            'username_or_email_to_export',
     827                            __( 'Confirmation request initiated successfully.' ),
     828                            'updated'
     829                        );
     830                    }
     831                }
     832                break;
     833        }
     834    }
     835}
     836
     837/**
     838 * Personal data export.
     839 *
     840 * @since 5.0.0
     841 * @access private
     842 */
     843function _wp_personal_data_export_page() {
     844    if ( ! current_user_can( 'manage_options' ) ) {
     845        wp_die( esc_html__( 'Sorry, you are not allowed to manage privacy on this site.' ) );
     846    }
     847
     848    _wp_personal_data_handle_actions();
     849
     850    $requests_table = new WP_Privacy_Data_Export_Requests_Table( array(
     851        'plural'   => 'privacy_requests',
     852        'singular' => 'privacy_request',
     853    ) );
     854    $requests_table->process_bulk_action();
     855    $requests_table->prepare_items();
     856    ?>
     857    <div class="wrap nosubsub">
     858        <h1><?php esc_html_e( 'Export Personal Data' ); ?></h1>
     859        <hr class="wp-header-end" />
     860
     861        <?php settings_errors(); ?>
     862
     863        <form method="post" class="wp-privacy-request-form">
     864            <h2><?php esc_html_e( 'Add Data Export Request' ); ?></h2>
     865            <p><?php esc_html_e( 'An email will be sent to the user at this email address asking them to verify the request.' ); ?></p>
     866
     867            <div class="wp-privacy-request-form-field">
     868                <label for="username_or_email_to_export"><?php esc_html_e( 'Username or email address' ); ?></label>
     869                <input type="text" required class="regular-text" id="username_or_email_to_export" name="username_or_email_to_export" />
     870                <?php submit_button( __( 'Send Request' ), 'secondary', 'submit', false ); ?>
     871            </div>
     872            <?php wp_nonce_field( 'personal-data-request' ); ?>
     873            <input type="hidden" name="action" value="add_export_personal_data_request" />
     874            <input type="hidden" name="type_of_action" value="user_export_request" />
     875        </form>
     876        <hr />
     877
     878        <?php $requests_table->views(); ?>
     879
     880        <form class="search-form wp-clearfix">
     881            <?php $requests_table->search_box( __( 'Search Requests' ), 'requests' ); ?>
     882            <input type="hidden" name="page" value="export_personal_data" />
     883            <input type="hidden" name="filter-status" value="<?php echo isset( $_REQUEST['filter-status'] ) ? esc_attr( sanitize_text_field( $_REQUEST['filter-status'] ) ) : ''; ?>" />
     884            <input type="hidden" name="orderby" value="<?php echo isset( $_REQUEST['orderby'] ) ? esc_attr( sanitize_text_field( $_REQUEST['orderby'] ) ) : ''; ?>" />
     885            <input type="hidden" name="order" value="<?php echo isset( $_REQUEST['order'] ) ? esc_attr( sanitize_text_field( $_REQUEST['order'] ) ) : ''; ?>" />
     886        </form>
     887
     888        <form method="post">
     889            <?php
     890            $requests_table->display();
     891            $requests_table->embed_scripts();
     892            ?>
     893        </form>
     894    </div>
     895    <?php
     896}
     897
     898/**
     899 * Personal data anonymization.
     900 *
     901 * @since 5.0.0
     902 * @access private
     903 */
     904function _wp_personal_data_removal_page() {
     905    if ( ! current_user_can( 'manage_options' ) ) {
     906        wp_die( esc_html__( 'Sorry, you are not allowed to manage privacy on this site.' ) );
     907    }
     908
     909    _wp_personal_data_handle_actions();
     910
     911    $requests_table = new WP_Privacy_Data_Removal_Requests_Table( array(
     912        'plural'   => 'privacy_requests',
     913        'singular' => 'privacy_request',
     914    ) );
     915    $requests_table->process_bulk_action();
     916    $requests_table->prepare_items();
     917    ?>
     918    <div class="wrap nosubsub">
     919        <h1><?php esc_html_e( 'Remove Personal Data' ); ?></h1>
     920        <hr class="wp-header-end" />
     921
     922        <?php settings_errors(); ?>
     923
     924        <form method="post" class="wp-privacy-request-form">
     925            <h2><?php esc_html_e( 'Add Data Removal Request' ); ?></h2>
     926            <p><?php esc_html_e( 'An email will be sent to the user at this email address asking them to verify the request.' ); ?></p>
     927
     928            <div class="wp-privacy-request-form-field">
     929                <label for="username_or_email_to_export"><?php esc_html_e( 'Username or email address' ); ?></label>
     930                <input type="text" required class="regular-text" id="username_or_email_to_export" name="username_or_email_to_export" />
     931                <?php submit_button( __( 'Send Request' ), 'secondary', 'submit', false ); ?>
     932            </div>
     933            <?php wp_nonce_field( 'personal-data-request' ); ?>
     934            <input type="hidden" name="action" value="add_remove_personal_data_request" />
     935            <input type="hidden" name="type_of_action" value="user_remove_request" />
     936        </form>
     937        <hr />
     938
     939        <?php $requests_table->views(); ?>
     940
     941        <form class="search-form wp-clearfix">
     942            <?php $requests_table->search_box( __( 'Search Requests' ), 'requests' ); ?>
     943            <input type="hidden" name="page" value="export_personal_data" />
     944            <input type="hidden" name="filter-status" value="<?php echo isset( $_REQUEST['filter-status'] ) ? esc_attr( sanitize_text_field( $_REQUEST['filter-status'] ) ) : ''; ?>" />
     945            <input type="hidden" name="orderby" value="<?php echo isset( $_REQUEST['orderby'] ) ? esc_attr( sanitize_text_field( $_REQUEST['orderby'] ) ) : ''; ?>" />
     946            <input type="hidden" name="order" value="<?php echo isset( $_REQUEST['order'] ) ? esc_attr( sanitize_text_field( $_REQUEST['order'] ) ) : ''; ?>" />
     947        </form>
     948
     949        <form method="post">
     950            <?php
     951            $requests_table->display();
     952            $requests_table->embed_scripts();
     953            ?>
     954        </form>
     955    </div>
     956    <?php
     957}
     958
     959/**
     960 * Add requests pages.
     961 *
     962 * @since 5.0.0
     963 * @access private
     964 */
     965function _wp_privacy_hook_requests_page() {
     966    add_submenu_page( 'tools.php', __( 'Export Personal Data' ), __( 'Export Personal Data' ), 'manage_options', 'export_personal_data', '_wp_personal_data_export_page' );
     967    add_submenu_page( 'tools.php', __( 'Remove Personal Data' ), __( 'Remove Personal Data' ), 'manage_options', 'remove_personal_data', '_wp_personal_data_removal_page' );
     968}
     969
     970// TODO: move the following classes in new files.
     971if ( ! class_exists( 'WP_List_Table' ) ) {
     972    require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
     973}
     974
     975/**
     976 * WP_Privacy_Requests_Table class.
     977 */
     978abstract class WP_Privacy_Requests_Table extends WP_List_Table {
     979
     980    /**
     981     * Action name for the requests this table will work with. Classes
     982     * which inherit from WP_Privacy_Requests_Table should define this.
     983     * e.g. 'user_export_request'
     984     *
     985     * @since 5.0.0
     986     *
     987     * @var string $request_type Name of action.
     988     */
     989    protected $request_type = 'INVALID';
     990
     991    /**
     992     * Get columns to show in the list table.
     993     *
     994     * @since 5.0.0
     995     *
     996     * @param array Array of columns.
     997     */
     998    public function get_columns() {
     999        $columns = array(
     1000            'cb'         => '<input type="checkbox" />',
     1001            'email'      => __( 'Requester' ),
     1002            'status'     => __( 'Status' ),
     1003            'requested'  => __( 'Requested' ),
     1004            'next_steps' => __( 'Next Steps' ),
     1005        );
     1006        return $columns;
     1007    }
     1008
     1009    /**
     1010     * Get a list of sortable columns.
     1011     *
     1012     * @since 5.0.0
     1013     *
     1014     * @return array
     1015     */
     1016    protected function get_sortable_columns() {
     1017        return array();
     1018    }
     1019
     1020    /**
     1021     * Default primary column.
     1022     *
     1023     * @since 5.0.0
     1024     *
     1025     * @return string
     1026     */
     1027    protected function get_default_primary_column_name() {
     1028        return 'email';
     1029    }
     1030
     1031    /**
     1032     * Get an associative array ( id => link ) with the list
     1033     * of views available on this table.
     1034     *
     1035     * @since 5.0.0
     1036     *
     1037     * @return array
     1038     */
     1039    protected function get_views() {
     1040        $current_status = isset( $_REQUEST['filter-status'] ) ? sanitize_text_field( $_REQUEST['filter-status'] ): '';
     1041        $statuses       = _wp_privacy_statuses();
     1042        $views          = array();
     1043        $admin_url      = admin_url( 'tools.php?page=' . $this->request_type );
     1044        $counts         = wp_count_posts( $this->request_type );
     1045
     1046        $current_link_attributes = empty( $current_status ) ? ' class="current" aria-current="page"' : '';
     1047        $views['all']            = '<a href="' . esc_url( $admin_url ) . "\" $current_link_attributes>" . esc_html__( 'All' ) . ' (' . absint( array_sum( (array) $counts ) ) . ')</a>';
     1048
     1049        foreach ( $statuses as $status => $label ) {
     1050            $current_link_attributes = $status === $current_status ? ' class="current" aria-current="page"' : '';
     1051            $views[ $status ]        = '<a href="' . esc_url( add_query_arg( 'filter-status', $status, $admin_url ) ) . "\" $current_link_attributes>" . esc_html( $label ) . ' (' . absint( $counts->$status ) . ')</a>';
     1052        }
     1053
     1054        return $views;
     1055    }
     1056
     1057    /**
     1058     * Get bulk actions.
     1059     *
     1060     * @since 5.0.0
     1061     *
     1062     * @return array
     1063     */
     1064    protected function get_bulk_actions() {
     1065        return array(
     1066            'delete' => __( 'Remove' ),
     1067            'resend' => __( 'Resend email' ),
     1068        );
     1069    }
     1070
     1071    /**
     1072     * Process bulk actions.
     1073     *
     1074     * @since 5.0.0
     1075     */
     1076    public function process_bulk_action() {
     1077        $action      = $this->current_action();
     1078        $request_ids = isset( $_REQUEST['request_id'] ) ? wp_parse_id_list( wp_unslash( $_REQUEST['request_id'] ) ) : array(); // WPCS: input var ok, CSRF ok.
     1079
     1080        if ( $request_ids ) {
     1081            check_admin_referer( 'bulk-privacy_requests' );
     1082        }
     1083
     1084        switch ( $action ) {
     1085            case 'delete':
     1086                $count = 0;
     1087
     1088                foreach ( $request_ids as $request_id ) {
     1089                    if ( wp_delete_post( $request_id, true ) ) {
     1090                        $count ++;
     1091                    }
     1092                }
     1093
     1094                add_settings_error(
     1095                    'bulk_action',
     1096                    'bulk_action',
     1097                    sprintf( _n( 'Deleted %d request', 'Deleted %d requests', $count ), $count ),
     1098                    'updated'
     1099                );
     1100                break;
     1101            case 'resend':
     1102                $count = 0;
     1103
     1104                foreach ( $request_ids as $request_id ) {
     1105                    if ( _wp_privacy_resend_request( $request_id ) ) {
     1106                        $count ++;
     1107                    }
     1108                }
     1109
     1110                add_settings_error(
     1111                    'bulk_action',
     1112                    'bulk_action',
     1113                    sprintf( _n( 'Re-sent %d request', 'Re-sent %d requests', $count ), $count ),
     1114                    'updated'
     1115                );
     1116                break;
     1117        }
     1118    }
     1119
     1120    /**
     1121     * Prepare items to output.
     1122     *
     1123     * @since 5.0.0
     1124     */
     1125    public function prepare_items() {
     1126        global $wpdb;
     1127
     1128        $primary               = $this->get_primary_column_name();
     1129        $this->_column_headers = array(
     1130            $this->get_columns(),
     1131            array(),
     1132            $this->get_sortable_columns(),
     1133            $primary,
     1134        );
     1135
     1136        $this->items    = array();
     1137        $posts_per_page = 20;
     1138        $args           = array(
     1139            'post_type'      => $this->request_type,
     1140            'posts_per_page' => $posts_per_page,
     1141            'offset'         => isset( $_REQUEST['paged'] ) ? max( 0, absint( $_REQUEST['paged'] ) - 1 ) * $posts_per_page: 0,
     1142            'post_status'    => 'any',
     1143        );
     1144
     1145        if ( ! empty( $_REQUEST['filter-status'] ) ) {
     1146            $filter_status       = isset( $_REQUEST['filter-status'] ) ? sanitize_text_field( $_REQUEST['filter-status'] ) : '';
     1147            $args['post_status'] = $filter_status;
     1148        }
     1149
     1150        if ( ! empty( $_REQUEST['s'] ) ) {
     1151            $args['meta_query'] = array(
     1152                $name_query,
     1153                'relation'  => 'AND',
     1154                array(
     1155                    'key'     => '_user_email',
     1156                    'value'   => isset( $_REQUEST['s'] ) ? sanitize_text_field( $_REQUEST['s'] ): '',
     1157                    'compare' => 'LIKE'
     1158                ),
     1159            );
     1160        }
     1161
     1162        $privacy_requests_query = new WP_Query( $args );
     1163        $privacy_requests       = $privacy_requests_query->posts;
     1164
     1165        foreach ( $privacy_requests as $privacy_request ) {
     1166            $this->items[] = array(
     1167                'request_id' => $privacy_request->ID,
     1168                'user_id'    => $privacy_request->post_author,
     1169                'email'      => get_post_meta( $privacy_request->ID, '_user_email', true ),
     1170                'action'     => get_post_meta( $privacy_request->ID, '_action_name', true ),
     1171                'requested'  => strtotime( $privacy_request->post_date_gmt ),
     1172                'confirmed'  => get_post_meta( $privacy_request->ID, '_confirmed_timestamp', true ),
     1173                'completed'  => get_post_meta( $privacy_request->ID, '_completed_timestamp', true ),
     1174            );
     1175        }
     1176
     1177        $this->set_pagination_args(
     1178            array(
     1179                'total_items' => $privacy_requests_query->found_posts,
     1180                'per_page'    => $posts_per_page,
     1181            )
     1182        );
     1183    }
     1184
     1185    /**
     1186     * Checkbox column.
     1187     *
     1188     * @since 5.0.0
     1189     *
     1190     * @param array $item Item being shown.
     1191     * @return string
     1192     */
     1193    public function column_cb( $item ) {
     1194        return sprintf( '<input type="checkbox" name="request_id[]" value="%1$s" /><span class="spinner"></span>', esc_attr( $item['request_id'] ) );
     1195    }
     1196
     1197    /**
     1198     * Status column.
     1199     *
     1200     * @since 5.0.0
     1201     *
     1202     * @param array $item Item being shown.
     1203     * @return string
     1204     */
     1205    public function column_status( $item ) {
     1206        $status        = get_post_status( $item['request_id'] );
     1207        $status_object = get_post_status_object( $status );
     1208
     1209        if ( ! $status_object || empty( $status_object->label ) ) {
     1210            return '-';
     1211        }
     1212
     1213        $timestamp = false;
     1214
     1215        switch ( $status ) {
     1216            case 'request-confirmed':
     1217                $timestamp = $item['confirmed'];
     1218                break;
     1219            case 'request-completed':
     1220                $timestamp = $item['completed'];
     1221                break;
     1222        }
     1223
     1224        echo '<span class="status-label status-' . esc_attr( $status ) . '">';
     1225        echo esc_html( $status_object->label );
     1226
     1227        if ( $timestamp ) {
     1228            echo ' (' . $this->get_timestamp_as_date( $timestamp ) . ')';
     1229        }
     1230
     1231        echo '</span>';
     1232    }
     1233
     1234    /**
     1235     * Convert timestamp for display.
     1236     *
     1237     * @since 5.0.0
     1238     *
     1239     * @param int $timestamp Event timestamp.
     1240     * @return string
     1241     */
     1242    protected function get_timestamp_as_date( $timestamp ) {
     1243        if ( empty( $timestamp ) ) {
     1244            return '';
     1245        }
     1246
     1247        $time_diff = current_time( 'timestamp', true ) - $timestamp;
     1248
     1249        if ( $time_diff >= 0 && $time_diff < DAY_IN_SECONDS ) {
     1250            return sprintf( __( '%s ago' ), human_time_diff( $timestamp ) );
     1251        }
     1252
     1253        return date_i18n( get_option( 'date_format' ), $timestamp );
     1254    }
     1255
     1256    /**
     1257     * Default column handler.
     1258     *
     1259     * @since 5.0.0
     1260     *
     1261     * @param array $item         Item being shown.
     1262     * @param string $column_name Name of column being shown.
     1263     * @return string
     1264     */
     1265    public function column_default( $item, $column_name ) {
     1266        $cell_value = $item[ $column_name ];
     1267
     1268        if ( in_array( $column_name, array( 'requested' ), true ) ) {
     1269            return $this->get_timestamp_as_date( $cell_value );
     1270        }
     1271
     1272        return $cell_value;
     1273    }
     1274
     1275    /**
     1276     * Actions column. Overriden by children.
     1277     *
     1278     * @since 5.0.0
     1279     *
     1280     * @param array $item Item being shown.
     1281     * @return string
     1282     */
     1283    public function column_email( $item ) {
     1284        return sprintf( '%1$s %2$s', $item['email'], $this->row_actions( array() ) );
     1285    }
     1286
     1287    /**
     1288     * Next steps column. Overriden by children.
     1289     *
     1290     * @since 5.0.0
     1291     *
     1292     * @param array $item Item being shown.
     1293     */
     1294    public function column_next_steps( $item ) {}
     1295
     1296    /**
     1297     * Generates content for a single row of the table
     1298     *
     1299     * @since 5.0.0
     1300     *
     1301     * @param object $item The current item
     1302     */
     1303    public function single_row( $item ) {
     1304        $status = get_post_status( $item['request_id'] );
     1305
     1306        echo '<tr class="status-' . esc_attr( $status ) . '">';
     1307        $this->single_row_columns( $item );
     1308        echo '</tr>';
     1309    }
     1310
     1311    /**
     1312     * Embed scripts used to perform actions. Overriden by children.
     1313     *
     1314     * @since 5.0.0
     1315     */
     1316    public function embed_scripts() {}
     1317}
     1318
     1319/**
     1320 * WP_Privacy_Data_Export_Requests_Table class.
     1321 *
     1322 * @since 5.0.0
     1323 */
     1324class WP_Privacy_Data_Export_Requests_Table extends WP_Privacy_Requests_Table {
     1325    /**
     1326     * Action name for the requests this table will work with. Classes
     1327     * which inherit from WP_Privacy_Requests_Table should define this.
     1328     * e.g. 'user_export_request'
     1329     *
     1330     * @since 5.0.0
     1331     *
     1332     * @var string $request_type Name of action.
     1333     */
     1334    protected $request_type = 'user_export_request';
     1335
     1336    /**
     1337     * Actions column.
     1338     *
     1339     * @since 5.0.0
     1340     *
     1341     * @param array $item Item being shown.
     1342     * @return string
     1343     */
     1344    public function column_email( $item ) {
     1345        $row_actions = array(
     1346            'download_data' => __( 'Download Personal Data' ),
     1347        );
     1348
     1349        return sprintf( '%1$s %2$s', $item['email'], $this->row_actions( $row_actions ) );
     1350    }
     1351
     1352    /**
     1353     * Next steps column.
     1354     *
     1355     * @since 5.0.0
     1356     *
     1357     * @param array $item Item being shown.
     1358     */
     1359    public function column_next_steps( $item ) {
     1360        $status = get_post_status( $item['request_id'] );
     1361
     1362        switch ( $status ) {
     1363            case 'request-pending':
     1364                esc_html_e( 'Waiting for confirmation' );
     1365                break;
     1366            case 'request-confirmed':
     1367                // TODO Complete in follow on patch.
     1368                break;
     1369            case 'request-failed':
     1370                submit_button( __( 'Retry' ), 'secondary', 'export_personal_data_email_retry[' . $item['request_id'] . ']', false );
     1371                break;
     1372            case 'request-completed':
     1373                echo '<a href="' . esc_url( wp_nonce_url( add_query_arg( array(
     1374                    'action' => 'delete',
     1375                    'request_id' => array( $item['request_id'] )
     1376                ), admin_url( 'tools.php?page=export_personal_data' ) ), 'bulk-privacy_requests' ) ) . '">' . esc_html__( 'Remove request' ) . '</a>';
     1377                break;
     1378        }
     1379    }
     1380}
     1381
     1382/**
     1383 * WP_Privacy_Data_Removal_Requests_Table class.
     1384 *
     1385     * @since 5.0.0
     1386 */
     1387class WP_Privacy_Data_Removal_Requests_Table extends WP_Privacy_Requests_Table {
     1388    /**
     1389     * Action name for the requests this table will work with. Classes
     1390     * which inherit from WP_Privacy_Requests_Table should define this.
     1391     * e.g. 'user_remove_request'
     1392     *
     1393     * @since 5.0.0
     1394     *
     1395     * @var string $request_type Name of action.
     1396     */
     1397    protected $request_type = 'user_remove_request';
     1398
     1399    /**
     1400     * Actions column.
     1401     *
     1402     * @since 5.0.0
     1403     *
     1404     * @param array $item Item being shown.
     1405     * @return string
     1406     */
     1407    public function column_email( $item ) {
     1408        $row_actions = array(
     1409            // TODO Complete in follow on patch.
     1410            'remove_data' => __( 'Remove Personal Data' ),
     1411        );
     1412
     1413        // If we have a user ID, include a delete user action.
     1414        if ( ! empty( $item['user_id'] ) ) {
     1415            // TODO Complete in follow on patch.
     1416            $row_actions['delete_user'] = __( 'Delete User' );
     1417        }
     1418
     1419        return sprintf( '%1$s %2$s', $item['email'], $this->row_actions( $row_actions ) );
     1420    }
     1421
     1422    /**
     1423     * Next steps column.
     1424     *
     1425     * @since 5.0.0
     1426     *
     1427     * @param array $item Item being shown.
     1428     */
     1429    public function column_next_steps( $item ) {
     1430    }
     1431
     1432}
  • trunk/src/wp-includes/post.php

    r42876 r42967  
    227227    );
    228228
     229    register_post_type(
     230        'user_export_request', array(
     231            'labels'           => array(
     232                'name'          => __( 'Export Personal Data Requests' ),
     233                'singular_name' => __( 'Export Personal Data Request' ),
     234            ),
     235            'public'           => false,
     236            '_builtin'         => true, /* internal use only. don't use this when registering your own post type. */
     237            'hierarchical'     => false,
     238            'rewrite'          => false,
     239            'query_var'        => false,
     240            'can_export'       => false,
     241            'delete_with_user' => false,
     242        )
     243    );
     244
     245    register_post_type(
     246        'user_remove_request', array(
     247            'labels'           => array(
     248                'name'          => __( 'Remove Personal Data Requests' ),
     249                'singular_name' => __( 'Remove Personal Data Request' ),
     250            ),
     251            'public'           => false,
     252            '_builtin'         => true, /* internal use only. don't use this when registering your own post type. */
     253            'hierarchical'     => false,
     254            'rewrite'          => false,
     255            'query_var'        => false,
     256            'can_export'       => false,
     257            'delete_with_user' => false,
     258        )
     259    );
     260
    229261    register_post_status(
    230262        'publish', array(
     
    293325        'inherit', array(
    294326            'label'               => 'inherit',
     327            'internal'            => true,
     328            '_builtin'            => true, /* internal use only. */
     329            'exclude_from_search' => false,
     330        )
     331    );
     332
     333    register_post_status(
     334        'request-pending', array(
     335            'label'               => _x( 'Pending', 'request status' ),
     336            'internal'            => true,
     337            '_builtin'            => true, /* internal use only. */
     338            'exclude_from_search' => false,
     339        )
     340    );
     341
     342    register_post_status(
     343        'request-confirmed', array(
     344            'label'               => _x( 'Confirmed', 'request status' ),
     345            'internal'            => true,
     346            '_builtin'            => true, /* internal use only. */
     347            'exclude_from_search' => false,
     348        )
     349    );
     350
     351    register_post_status(
     352        'request-failed', array(
     353            'label'               => _x( 'Failed', 'request status' ),
     354            'internal'            => true,
     355            '_builtin'            => true, /* internal use only. */
     356            'exclude_from_search' => false,
     357        )
     358    );
     359
     360    register_post_status(
     361        'request-completed', array(
     362            'label'               => _x( 'Completed', 'request status' ),
    295363            'internal'            => true,
    296364            '_builtin'            => true, /* internal use only. */
     
    781849
    782850    return $status;
     851}
     852
     853/**
     854 * Return statuses for privacy requests.
     855 *
     856 * @since 5.0.0
     857 *
     858 * @return array
     859 */
     860function _wp_privacy_statuses() {
     861    return array(
     862        'request-pending'   => __( 'Pending' ),      // Pending confirmation from user.
     863        'request-confirmed' => __( 'Confirmed' ),    // User has confirmed the action.
     864        'request-failed'    => __( 'Failed' ),       // User failed to confirm the action.
     865        'request-completed' => __( 'Completed' ),    // Admin has handled the request.
     866    );
    783867}
    784868
  • trunk/src/wp-includes/user.php

    r42964 r42967  
    28122812
    28132813/**
     2814 * Get all user privacy request types.
     2815 *
     2816 * @since 5.0.0
     2817 * @access private
     2818 *
     2819 * @return array
     2820 */
     2821function _wp_privacy_action_request_types() {
     2822    return array(
     2823        'user_export_request',
     2824        'user_remove_request',
     2825    );
     2826}
     2827
     2828/**
     2829 * Update log when privacy request is confirmed.
     2830 *
     2831 * @since 5.0.0
     2832 * @access private
     2833 *
     2834 * @param array $result Result of the request from the user.
     2835 */
     2836function _wp_privacy_account_request_confirmed( $result ) {
     2837    if ( isset( $result['action'], $result['request_data'], $result['request_data']['privacy_request_id'] ) && in_array( $result['action'], _wp_privacy_action_request_types(), true ) ) {
     2838        $privacy_request_id = absint( $result['request_data']['privacy_request_id'] );
     2839        $privacy_request    = get_post( $privacy_request_id );
     2840
     2841        if ( ! $privacy_request || ! in_array( $privacy_request->post_type, _wp_privacy_action_request_types(), true ) ) {
     2842            return;
     2843        }
     2844
     2845        update_post_meta( $privacy_request_id, '_confirmed_timestamp', time() );
     2846        wp_update_post( array(
     2847            'ID'          => $privacy_request_id,
     2848            'post_status' => 'request-confirmed',
     2849        ) );
     2850    }
     2851}
     2852add_action( 'account_action_confirmed', '_wp_privacy_account_request_confirmed' );
     2853
     2854/**
     2855 * Update log when privacy request fails.
     2856 *
     2857 * @since 5.0.0
     2858 * @access private
     2859 *
     2860 * @param array $result Result of the request from the user.
     2861 */
     2862function _wp_privacy_account_request_failed( $result ) {
     2863    if ( isset( $result['action'], $result['request_data'], $result['request_data']['privacy_request_id'] ) &&
     2864        in_array( $result['action'], _wp_privacy_action_request_types(), true ) ) {
     2865
     2866        $privacy_request_id = absint( $result['request_data']['privacy_request_id'] );
     2867        $privacy_request    = get_post( $privacy_request_id );
     2868
     2869        if ( ! $privacy_request || ! in_array( $privacy_request->post_type, _wp_privacy_action_request_types(), true ) ) {
     2870            return;
     2871        }
     2872
     2873        wp_update_post( array(
     2874            'ID'          => $privacy_request_id,
     2875            'post_status' => 'request-failed',
     2876        ) );
     2877    }
     2878}
     2879
     2880/**
    28142881 * Send a confirmation request email to confirm an action.
    28152882 *
    28162883 * @since 5.0.0
    28172884 *
    2818  * @param string $email              User email address. This can be the address of a registered or non-registered user. Defaults to logged in user email address. 
     2885 * @param string $email              User email address. This can be the address of a registered or non-registered user. Defaults to logged in user email address.
    28192886 * @param string $action_name        Name of the action that is being confirmed. Defaults to 'confirm_email'.
    28202887 * @param string $action_description User facing description of the action they will be confirming. Defaults to "confirm your email address".
     
    29182985     *
    29192986     * @since 5.0.0
    2920      * 
     2987     *
    29212988     * @param string $email_text     Text in the email.
    29222989     * @param array  $email_data {
     
    30403107        $email    = $user->user_email;
    30413108
    3042         if ( false !== strpos( $confirm_action_data, ':' ) ) {
    3043             list( $key_request_time, $saved_key ) = explode( ':', $confirm_action_data, 2 );
     3109        if ( false !== strpos( $raw_data, ':' ) ) {
     3110            list( $key_request_time, $saved_key ) = explode( ':', $raw_data, 2 );
    30443111        }
    30453112    } else {
    30463113        $raw_data = get_site_option( '_verify_action_' . $action_name . '_' . $uid, '' );
    30473114
    3048         if ( false !== strpos( $confirm_action_data, ':' ) ) {
    3049             list( $key_request_time, $saved_key, $email ) = explode( ':', $confirm_action_data, 3 );
     3115        if ( false !== strpos( $raw_data, ':' ) ) {
     3116            list( $key_request_time, $saved_key, $email ) = explode( ':', $raw_data, 3 );
    30503117        }
    30513118    }
     
    30693136     *
    30703137     * @since 5.0.0
    3071      * 
     3138     *
    30723139     * @param int $expiration The expiration time in seconds.
    30733140     */
Note: See TracChangeset for help on using the changeset viewer.