Make WordPress Core

Ticket #43481: 43481.diff

File 43481.diff, 34.2 KB (added by allendav, 7 years ago)

Latest UX for export and erasure

  • src/wp-admin/css/forms.css

     
    14031403                margin-right: 0.5em;
    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}
  • src/wp-admin/includes/user.php

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

     
    226226                )
    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(
    231263                        'label'       => _x( 'Published', 'post status' ),
     
    297329                        'exclude_from_search' => false,
    298330                )
    299331        );
     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' ),
     363                        'internal'            => true,
     364                        '_builtin'            => true, /* internal use only. */
     365                        'exclude_from_search' => false,
     366                )
     367        );
    300368}
    301369
    302370/**
     
    783851}
    784852
    785853/**
     854 * Return statuses for privacy requests.
     855 *
     856 * @return array
     857 */
     858function _wp_privacy_statuses() {
     859        return array(
     860                'request-pending'   => __( 'Pending' ),      // Pending confirmation from user.
     861                'request-confirmed' => __( 'Confirmed' ),    // User has confirmed the action.
     862                'request-failed'    => __( 'Failed' ),       // User failed to confirm the action.
     863                'request-completed' => __( 'Completed' ),    // Admin has handled the request.
     864        );
     865}
     866
     867/**
    786868 * Register a post status. Do not use before init.
    787869 *
    788870 * A simple function for creating or modifying a post status based on the
  • src/wp-includes/user.php

     
    28112811}
    28122812
    28132813/**
     2814 * Get all user privacy request types.
     2815 *
     2816 * @return array
     2817 */
     2818function _wp_privacy_action_request_types() {
     2819        return array(
     2820                'user_export_request',
     2821                'user_remove_request',
     2822        );
     2823}
     2824
     2825/**
     2826 * Update log when privacy request is confirmed.
     2827 *
     2828 * @param array $result Result of the request from the user.
     2829 */
     2830function _wp_privacy_account_request_confirmed( $result ) {
     2831        if ( isset( $result['action'], $result['request_data'], $result['request_data']['privacy_request_id'] ) && in_array( $result['action'], _wp_privacy_action_request_types(), true ) ) {
     2832                $privacy_request_id = absint( $result['request_data']['privacy_request_id'] );
     2833                $privacy_request    = get_post( $privacy_request_id );
     2834
     2835                if ( ! $privacy_request || ! in_array( $privacy_request->post_type, _wp_privacy_action_request_types(), true ) ) {
     2836                        return;
     2837                }
     2838
     2839                update_post_meta( $privacy_request_id, '_confirmed_timestamp', time() );
     2840                wp_update_post( array(
     2841                        'ID'          => $privacy_request_id,
     2842                        'post_status' => 'request-confirmed',
     2843                ) );
     2844        }
     2845}
     2846add_action( 'account_action_confirmed', '_wp_privacy_account_request_confirmed' );
     2847
     2848/**
     2849 * Update log when privacy request fails.
     2850 *
     2851 * @param array $result Result of the request from the user.
     2852 */
     2853function _wp_privacy_account_request_failed( $result ) {
     2854        if ( isset( $result['action'], $result['request_data'], $result['request_data']['privacy_request_id'] ) && in_array( $result['action'], _wp_privacy_action_request_types(), true ) ) {
     2855                $privacy_request_id = absint( $result['request_data']['privacy_request_id'] );
     2856                $privacy_request    = get_post( $privacy_request_id );
     2857
     2858                if ( ! $privacy_request || ! in_array( $privacy_request->post_type, _wp_privacy_action_request_types(), true ) ) {
     2859                        return;
     2860                }
     2861
     2862                wp_update_post( array(
     2863                        'ID'          => $privacy_request_id,
     2864                        'post_status' => 'request-failed',
     2865                ) );
     2866        }
     2867}
     2868add_action( 'account_action_failed', '_wp_privacy_account_request_failed' );
     2869
     2870/**
    28142871 * Send a confirmation request email to confirm an action.
    28152872 *
    28162873 * @since 5.0.0
    28172874 *
    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. 
     2875 * @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.
    28192876 * @param string $action_name        Name of the action that is being confirmed. Defaults to 'confirm_email'.
    28202877 * @param string $action_description User facing description of the action they will be confirming. Defaults to "confirm your email address".
    28212878 * @param array  $request_data       Misc data you want to send with the verification request and pass to the actions once the request is confirmed.
     
    29172974         * ###SITEURL###            The URL to the site.
    29182975         *
    29192976         * @since 5.0.0
    2920          * 
     2977         *
    29212978         * @param string $email_text     Text in the email.
    29222979         * @param array  $email_data {
    29232980         *     Data relating to the account action email.
     
    30683125         * Filters the expiration time of confirm keys.
    30693126         *
    30703127         * @since 5.0.0
    3071          * 
     3128         *
    30723129         * @param int $expiration The expiration time in seconds.
    30733130         */
    30743131        $expiration_duration = apply_filters( 'account_verification_expiration', DAY_IN_SECONDS );
     
    30963153        }
    30973154
    30983155        return $return;
    3099 }
    3100  No newline at end of file
     3156}