Make WordPress Core

Changeset 42967 for trunk


Ignore:
Timestamp:
04/10/2018 06:01:20 PM (8 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.