Make WordPress Core


Ignore:
Timestamp:
05/26/2019 08:49:04 PM (6 years ago)
Author:
azaozz
Message:

Privacy tools: Organize privacy functions into logical files and classes.

Props xkon, birgire, desrosj, garrett-eclipse, azaozz.
See #43895.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/user.php

    r45194 r45448  
    583583    );
    584584}
    585 
    586 /**
    587  * Resend an existing request and return the result.
    588  *
    589  * @since 4.9.6
    590  * @access private
    591  *
    592  * @param int $request_id Request ID.
    593  * @return bool|WP_Error Returns true/false based on the success of sending the email, or a WP_Error object.
    594  */
    595 function _wp_privacy_resend_request( $request_id ) {
    596     $request_id = absint( $request_id );
    597     $request    = get_post( $request_id );
    598 
    599     if ( ! $request || 'user_request' !== $request->post_type ) {
    600         return new WP_Error( 'privacy_request_error', __( 'Invalid request.' ) );
    601     }
    602 
    603     $result = wp_send_user_request( $request_id );
    604 
    605     if ( is_wp_error( $result ) ) {
    606         return $result;
    607     } elseif ( ! $result ) {
    608         return new WP_Error( 'privacy_request_error', __( 'Unable to initiate confirmation request.' ) );
    609     }
    610 
    611     return true;
    612 }
    613 
    614 /**
    615  * Marks a request as completed by the admin and logs the current timestamp.
    616  *
    617  * @since 4.9.6
    618  * @access private
    619  *
    620  * @param  int          $request_id Request ID.
    621  * @return int|WP_Error $result Request ID on success or WP_Error.
    622  */
    623 function _wp_privacy_completed_request( $request_id ) {
    624     $request_id = absint( $request_id );
    625     $request    = wp_get_user_request_data( $request_id );
    626 
    627     if ( ! $request ) {
    628         return new WP_Error( 'privacy_request_error', __( 'Invalid request.' ) );
    629     }
    630 
    631     update_post_meta( $request_id, '_wp_user_request_completed_timestamp', time() );
    632 
    633     $result = wp_update_post(
    634         array(
    635             'ID'          => $request_id,
    636             'post_status' => 'request-completed',
    637         )
    638     );
    639 
    640     return $result;
    641 }
    642 
    643 /**
    644  * Handle list table actions.
    645  *
    646  * @since 4.9.6
    647  * @access private
    648  */
    649 function _wp_personal_data_handle_actions() {
    650     if ( isset( $_POST['privacy_action_email_retry'] ) ) {
    651         check_admin_referer( 'bulk-privacy_requests' );
    652 
    653         $request_id = absint( current( array_keys( (array) wp_unslash( $_POST['privacy_action_email_retry'] ) ) ) );
    654         $result     = _wp_privacy_resend_request( $request_id );
    655 
    656         if ( is_wp_error( $result ) ) {
    657             add_settings_error(
    658                 'privacy_action_email_retry',
    659                 'privacy_action_email_retry',
    660                 $result->get_error_message(),
    661                 'error'
    662             );
    663         } else {
    664             add_settings_error(
    665                 'privacy_action_email_retry',
    666                 'privacy_action_email_retry',
    667                 __( 'Confirmation request sent again successfully.' ),
    668                 'updated'
    669             );
    670         }
    671     } elseif ( isset( $_POST['action'] ) ) {
    672         $action = isset( $_POST['action'] ) ? sanitize_key( wp_unslash( $_POST['action'] ) ) : '';
    673 
    674         switch ( $action ) {
    675             case 'add_export_personal_data_request':
    676             case 'add_remove_personal_data_request':
    677                 check_admin_referer( 'personal-data-request' );
    678 
    679                 if ( ! isset( $_POST['type_of_action'], $_POST['username_or_email_for_privacy_request'] ) ) {
    680                     add_settings_error(
    681                         'action_type',
    682                         'action_type',
    683                         __( 'Invalid action.' ),
    684                         'error'
    685                     );
    686                 }
    687                 $action_type               = sanitize_text_field( wp_unslash( $_POST['type_of_action'] ) );
    688                 $username_or_email_address = sanitize_text_field( wp_unslash( $_POST['username_or_email_for_privacy_request'] ) );
    689                 $email_address             = '';
    690 
    691                 if ( ! in_array( $action_type, _wp_privacy_action_request_types(), true ) ) {
    692                     add_settings_error(
    693                         'action_type',
    694                         'action_type',
    695                         __( 'Invalid action.' ),
    696                         'error'
    697                     );
    698                 }
    699 
    700                 if ( ! is_email( $username_or_email_address ) ) {
    701                     $user = get_user_by( 'login', $username_or_email_address );
    702                     if ( ! $user instanceof WP_User ) {
    703                         add_settings_error(
    704                             'username_or_email_for_privacy_request',
    705                             'username_or_email_for_privacy_request',
    706                             __( 'Unable to add this request. A valid email address or username must be supplied.' ),
    707                             'error'
    708                         );
    709                     } else {
    710                         $email_address = $user->user_email;
    711                     }
    712                 } else {
    713                     $email_address = $username_or_email_address;
    714                 }
    715 
    716                 if ( empty( $email_address ) ) {
    717                     break;
    718                 }
    719 
    720                 $request_id = wp_create_user_request( $email_address, $action_type );
    721 
    722                 if ( is_wp_error( $request_id ) ) {
    723                     add_settings_error(
    724                         'username_or_email_for_privacy_request',
    725                         'username_or_email_for_privacy_request',
    726                         $request_id->get_error_message(),
    727                         'error'
    728                     );
    729                     break;
    730                 } elseif ( ! $request_id ) {
    731                     add_settings_error(
    732                         'username_or_email_for_privacy_request',
    733                         'username_or_email_for_privacy_request',
    734                         __( 'Unable to initiate confirmation request.' ),
    735                         'error'
    736                     );
    737                     break;
    738                 }
    739 
    740                 wp_send_user_request( $request_id );
    741 
    742                 add_settings_error(
    743                     'username_or_email_for_privacy_request',
    744                     'username_or_email_for_privacy_request',
    745                     __( 'Confirmation request initiated successfully.' ),
    746                     'updated'
    747                 );
    748                 break;
    749         }
    750     }
    751 }
    752 
    753 /**
    754  * Cleans up failed and expired requests before displaying the list table.
    755  *
    756  * @since 4.9.6
    757  * @access private
    758  */
    759 function _wp_personal_data_cleanup_requests() {
    760     /** This filter is documented in wp-includes/user.php */
    761     $expires = (int) apply_filters( 'user_request_key_expiration', DAY_IN_SECONDS );
    762 
    763     $requests_query = new WP_Query(
    764         array(
    765             'post_type'      => 'user_request',
    766             'posts_per_page' => -1,
    767             'post_status'    => 'request-pending',
    768             'fields'         => 'ids',
    769             'date_query'     => array(
    770                 array(
    771                     'column' => 'post_modified_gmt',
    772                     'before' => $expires . ' seconds ago',
    773                 ),
    774             ),
    775         )
    776     );
    777 
    778     $request_ids = $requests_query->posts;
    779 
    780     foreach ( $request_ids as $request_id ) {
    781         wp_update_post(
    782             array(
    783                 'ID'            => $request_id,
    784                 'post_status'   => 'request-failed',
    785                 'post_password' => '',
    786             )
    787         );
    788     }
    789 }
    790 
    791 /**
    792  * Personal data export.
    793  *
    794  * @since 4.9.6
    795  * @access private
    796  */
    797 function _wp_personal_data_export_page() {
    798     if ( ! current_user_can( 'export_others_personal_data' ) ) {
    799         wp_die( __( 'Sorry, you are not allowed to export personal data on this site.' ) );
    800     }
    801 
    802     _wp_personal_data_handle_actions();
    803     _wp_personal_data_cleanup_requests();
    804 
    805     // "Borrow" xfn.js for now so we don't have to create new files.
    806     wp_enqueue_script( 'xfn' );
    807 
    808     $requests_table = new WP_Privacy_Data_Export_Requests_Table(
    809         array(
    810             'plural'   => 'privacy_requests',
    811             'singular' => 'privacy_request',
    812             'screen'   => 'export_personal_data',
    813         )
    814     );
    815 
    816     $requests_table->screen->set_screen_reader_content(
    817         array(
    818             'heading_views'      => __( 'Filter export personal data list' ),
    819             'heading_pagination' => __( 'Export personal data list navigation' ),
    820             'heading_list'       => __( 'Export personal data list' ),
    821         )
    822     );
    823 
    824     $requests_table->process_bulk_action();
    825     $requests_table->prepare_items();
    826     ?>
    827     <div class="wrap nosubsub">
    828         <h1><?php esc_html_e( 'Export Personal Data' ); ?></h1>
    829         <hr class="wp-header-end" />
    830 
    831         <?php settings_errors(); ?>
    832 
    833         <form action="<?php echo esc_url( admin_url( 'tools.php?page=export_personal_data' ) ); ?>" method="post" class="wp-privacy-request-form">
    834             <h2><?php esc_html_e( 'Add Data Export Request' ); ?></h2>
    835             <p><?php esc_html_e( 'An email will be sent to the user at this email address asking them to verify the request.' ); ?></p>
    836 
    837             <div class="wp-privacy-request-form-field">
    838                 <label for="username_or_email_for_privacy_request"><?php esc_html_e( 'Username or email address' ); ?></label>
    839                 <input type="text" required class="regular-text" id="username_or_email_for_privacy_request" name="username_or_email_for_privacy_request" />
    840                 <?php submit_button( __( 'Send Request' ), 'secondary', 'submit', false ); ?>
    841             </div>
    842             <?php wp_nonce_field( 'personal-data-request' ); ?>
    843             <input type="hidden" name="action" value="add_export_personal_data_request" />
    844             <input type="hidden" name="type_of_action" value="export_personal_data" />
    845         </form>
    846         <hr />
    847 
    848         <?php $requests_table->views(); ?>
    849 
    850         <form class="search-form wp-clearfix">
    851             <?php $requests_table->search_box( __( 'Search Requests' ), 'requests' ); ?>
    852             <input type="hidden" name="page" value="export_personal_data" />
    853             <input type="hidden" name="filter-status" value="<?php echo isset( $_REQUEST['filter-status'] ) ? esc_attr( sanitize_text_field( $_REQUEST['filter-status'] ) ) : ''; ?>" />
    854             <input type="hidden" name="orderby" value="<?php echo isset( $_REQUEST['orderby'] ) ? esc_attr( sanitize_text_field( $_REQUEST['orderby'] ) ) : ''; ?>" />
    855             <input type="hidden" name="order" value="<?php echo isset( $_REQUEST['order'] ) ? esc_attr( sanitize_text_field( $_REQUEST['order'] ) ) : ''; ?>" />
    856         </form>
    857 
    858         <form method="post">
    859             <?php
    860             $requests_table->display();
    861             $requests_table->embed_scripts();
    862             ?>
    863         </form>
    864     </div>
    865     <?php
    866 }
    867 
    868 /**
    869  * Personal data anonymization.
    870  *
    871  * @since 4.9.6
    872  * @access private
    873  */
    874 function _wp_personal_data_removal_page() {
    875     /*
    876      * Require both caps in order to make it explicitly clear that delegating
    877      * erasure from network admins to single-site admins will give them the
    878      * ability to affect global users, rather than being limited to the site
    879      * that they administer.
    880      */
    881     if ( ! current_user_can( 'erase_others_personal_data' ) || ! current_user_can( 'delete_users' ) ) {
    882         wp_die( __( 'Sorry, you are not allowed to erase data on this site.' ) );
    883     }
    884 
    885     _wp_personal_data_handle_actions();
    886     _wp_personal_data_cleanup_requests();
    887 
    888     // "Borrow" xfn.js for now so we don't have to create new files.
    889     wp_enqueue_script( 'xfn' );
    890 
    891     $requests_table = new WP_Privacy_Data_Removal_Requests_Table(
    892         array(
    893             'plural'   => 'privacy_requests',
    894             'singular' => 'privacy_request',
    895             'screen'   => 'remove_personal_data',
    896         )
    897     );
    898 
    899     $requests_table->screen->set_screen_reader_content(
    900         array(
    901             'heading_views'      => __( 'Filter erase personal data list' ),
    902             'heading_pagination' => __( 'Erase personal data list navigation' ),
    903             'heading_list'       => __( 'Erase personal data list' ),
    904         )
    905     );
    906 
    907     $requests_table->process_bulk_action();
    908     $requests_table->prepare_items();
    909 
    910     ?>
    911     <div class="wrap nosubsub">
    912         <h1><?php esc_html_e( 'Erase Personal Data' ); ?></h1>
    913         <hr class="wp-header-end" />
    914 
    915         <?php settings_errors(); ?>
    916 
    917         <form action="<?php echo esc_url( admin_url( 'tools.php?page=remove_personal_data' ) ); ?>" method="post" class="wp-privacy-request-form">
    918             <h2><?php esc_html_e( 'Add Data Erasure Request' ); ?></h2>
    919             <p><?php esc_html_e( 'An email will be sent to the user at this email address asking them to verify the request.' ); ?></p>
    920 
    921             <div class="wp-privacy-request-form-field">
    922                 <label for="username_or_email_for_privacy_request"><?php esc_html_e( 'Username or email address' ); ?></label>
    923                 <input type="text" required class="regular-text" id="username_or_email_for_privacy_request" name="username_or_email_for_privacy_request" />
    924                 <?php submit_button( __( 'Send Request' ), 'secondary', 'submit', false ); ?>
    925             </div>
    926             <?php wp_nonce_field( 'personal-data-request' ); ?>
    927             <input type="hidden" name="action" value="add_remove_personal_data_request" />
    928             <input type="hidden" name="type_of_action" value="remove_personal_data" />
    929         </form>
    930         <hr />
    931 
    932         <?php $requests_table->views(); ?>
    933 
    934         <form class="search-form wp-clearfix">
    935             <?php $requests_table->search_box( __( 'Search Requests' ), 'requests' ); ?>
    936             <input type="hidden" name="page" value="remove_personal_data" />
    937             <input type="hidden" name="filter-status" value="<?php echo isset( $_REQUEST['filter-status'] ) ? esc_attr( sanitize_text_field( $_REQUEST['filter-status'] ) ) : ''; ?>" />
    938             <input type="hidden" name="orderby" value="<?php echo isset( $_REQUEST['orderby'] ) ? esc_attr( sanitize_text_field( $_REQUEST['orderby'] ) ) : ''; ?>" />
    939             <input type="hidden" name="order" value="<?php echo isset( $_REQUEST['order'] ) ? esc_attr( sanitize_text_field( $_REQUEST['order'] ) ) : ''; ?>" />
    940         </form>
    941 
    942         <form method="post">
    943             <?php
    944             $requests_table->display();
    945             $requests_table->embed_scripts();
    946             ?>
    947         </form>
    948     </div>
    949     <?php
    950 }
    951 
    952 /**
    953  * Mark erasure requests as completed after processing is finished.
    954  *
    955  * This intercepts the Ajax responses to personal data eraser page requests, and
    956  * monitors the status of a request. Once all of the processing has finished, the
    957  * request is marked as completed.
    958  *
    959  * @since 4.9.6
    960  *
    961  * @see wp_privacy_personal_data_erasure_page
    962  *
    963  * @param array  $response      The response from the personal data eraser for
    964  *                              the given page.
    965  * @param int    $eraser_index  The index of the personal data eraser. Begins
    966  *                              at 1.
    967  * @param string $email_address The email address of the user whose personal
    968  *                              data this is.
    969  * @param int    $page          The page of personal data for this eraser.
    970  *                              Begins at 1.
    971  * @param int    $request_id    The request ID for this personal data erasure.
    972  * @return array The filtered response.
    973  */
    974 function wp_privacy_process_personal_data_erasure_page( $response, $eraser_index, $email_address, $page, $request_id ) {
    975     /*
    976      * If the eraser response is malformed, don't attempt to consume it; let it
    977      * pass through, so that the default Ajax processing will generate a warning
    978      * to the user.
    979      */
    980     if ( ! is_array( $response ) ) {
    981         return $response;
    982     }
    983 
    984     if ( ! array_key_exists( 'done', $response ) ) {
    985         return $response;
    986     }
    987 
    988     if ( ! array_key_exists( 'items_removed', $response ) ) {
    989         return $response;
    990     }
    991 
    992     if ( ! array_key_exists( 'items_retained', $response ) ) {
    993         return $response;
    994     }
    995 
    996     if ( ! array_key_exists( 'messages', $response ) ) {
    997         return $response;
    998     }
    999 
    1000     $request = wp_get_user_request_data( $request_id );
    1001 
    1002     if ( ! $request || 'remove_personal_data' !== $request->action_name ) {
    1003         wp_send_json_error( __( 'Invalid request ID when processing eraser data.' ) );
    1004     }
    1005 
    1006     /** This filter is documented in wp-admin/includes/ajax-actions.php */
    1007     $erasers        = apply_filters( 'wp_privacy_personal_data_erasers', array() );
    1008     $is_last_eraser = count( $erasers ) === $eraser_index;
    1009     $eraser_done    = $response['done'];
    1010 
    1011     if ( ! $is_last_eraser || ! $eraser_done ) {
    1012         return $response;
    1013     }
    1014 
    1015     _wp_privacy_completed_request( $request_id );
    1016 
    1017     /**
    1018      * Fires immediately after a personal data erasure request has been marked completed.
    1019      *
    1020      * @since 4.9.6
    1021      *
    1022      * @param int $request_id The privacy request post ID associated with this request.
    1023      */
    1024     do_action( 'wp_privacy_personal_data_erased', $request_id );
    1025 
    1026     return $response;
    1027 }
    1028 
    1029 /**
    1030  * Add requests pages.
    1031  *
    1032  * @since 4.9.6
    1033  * @access private
    1034  */
    1035 function _wp_privacy_hook_requests_page() {
    1036     add_submenu_page( 'tools.php', __( 'Export Personal Data' ), __( 'Export Personal Data' ), 'export_others_personal_data', 'export_personal_data', '_wp_personal_data_export_page' );
    1037     add_submenu_page( 'tools.php', __( 'Erase Personal Data' ), __( 'Erase Personal Data' ), 'erase_others_personal_data', 'remove_personal_data', '_wp_personal_data_removal_page' );
    1038 }
    1039 
    1040 /**
    1041  * Add options for the privacy requests screens.
    1042  *
    1043  * @since 4.9.8
    1044  * @access private
    1045  */
    1046 function _wp_privacy_requests_screen_options() {
    1047     $args = array(
    1048         'option' => str_replace( 'tools_page_', '', get_current_screen()->id ) . '_requests_per_page',
    1049     );
    1050     add_screen_option( 'per_page', $args );
    1051 }
    1052 
    1053 // TODO: move the following classes in new files.
    1054 if ( ! class_exists( 'WP_List_Table' ) ) {
    1055     require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
    1056 }
    1057 
    1058 /**
    1059  * WP_Privacy_Requests_Table class.
    1060  *
    1061  * @since 4.9.6
    1062  */
    1063 abstract class WP_Privacy_Requests_Table extends WP_List_Table {
    1064 
    1065     /**
    1066      * Action name for the requests this table will work with. Classes
    1067      * which inherit from WP_Privacy_Requests_Table should define this.
    1068      *
    1069      * Example: 'export_personal_data'.
    1070      *
    1071      * @since 4.9.6
    1072      *
    1073      * @var string $request_type Name of action.
    1074      */
    1075     protected $request_type = 'INVALID';
    1076 
    1077     /**
    1078      * Post type to be used.
    1079      *
    1080      * @since 4.9.6
    1081      *
    1082      * @var string $post_type The post type.
    1083      */
    1084     protected $post_type = 'INVALID';
    1085 
    1086     /**
    1087      * Get columns to show in the list table.
    1088      *
    1089      * @since 4.9.6
    1090      *
    1091      * @return array Array of columns.
    1092      */
    1093     public function get_columns() {
    1094         $columns = array(
    1095             'cb'                => '<input type="checkbox" />',
    1096             'email'             => __( 'Requester' ),
    1097             'status'            => __( 'Status' ),
    1098             'created_timestamp' => __( 'Requested' ),
    1099             'next_steps'        => __( 'Next Steps' ),
    1100         );
    1101         return $columns;
    1102     }
    1103 
    1104     /**
    1105      * Get a list of sortable columns.
    1106      *
    1107      * @since 4.9.6
    1108      *
    1109      * @return array Default sortable columns.
    1110      */
    1111     protected function get_sortable_columns() {
    1112         // The initial sorting is by 'Requested' (post_date) and descending.
    1113         // With initial sorting, the first click on 'Requested' should be ascending.
    1114         // With 'Requester' sorting active, the next click on 'Requested' should be descending.
    1115         $desc_first = isset( $_GET['orderby'] );
    1116 
    1117         return array(
    1118             'email'             => 'requester',
    1119             'created_timestamp' => array( 'requested', $desc_first ),
    1120         );
    1121     }
    1122 
    1123     /**
    1124      * Default primary column.
    1125      *
    1126      * @since 4.9.6
    1127      *
    1128      * @return string Default primary column name.
    1129      */
    1130     protected function get_default_primary_column_name() {
    1131         return 'email';
    1132     }
    1133 
    1134     /**
    1135      * Count number of requests for each status.
    1136      *
    1137      * @since 4.9.6
    1138      *
    1139      * @return object Number of posts for each status.
    1140      */
    1141     protected function get_request_counts() {
    1142         global $wpdb;
    1143 
    1144         $cache_key = $this->post_type . '-' . $this->request_type;
    1145         $counts    = wp_cache_get( $cache_key, 'counts' );
    1146 
    1147         if ( false !== $counts ) {
    1148             return $counts;
    1149         }
    1150 
    1151         $query = "
    1152             SELECT post_status, COUNT( * ) AS num_posts
    1153             FROM {$wpdb->posts}
    1154             WHERE post_type = %s
    1155             AND post_name = %s
    1156             GROUP BY post_status";
    1157 
    1158         $results = (array) $wpdb->get_results( $wpdb->prepare( $query, $this->post_type, $this->request_type ), ARRAY_A );
    1159         $counts  = array_fill_keys( get_post_stati(), 0 );
    1160 
    1161         foreach ( $results as $row ) {
    1162             $counts[ $row['post_status'] ] = $row['num_posts'];
    1163         }
    1164 
    1165         $counts = (object) $counts;
    1166         wp_cache_set( $cache_key, $counts, 'counts' );
    1167 
    1168         return $counts;
    1169     }
    1170 
    1171     /**
    1172      * Get an associative array ( id => link ) with the list of views available on this table.
    1173      *
    1174      * @since 4.9.6
    1175      *
    1176      * @return array Associative array of views in the format of $view_name => $view_markup.
    1177      */
    1178     protected function get_views() {
    1179         $current_status = isset( $_REQUEST['filter-status'] ) ? sanitize_text_field( $_REQUEST['filter-status'] ) : '';
    1180         $statuses       = _wp_privacy_statuses();
    1181         $views          = array();
    1182         $admin_url      = admin_url( 'tools.php?page=' . $this->request_type );
    1183         $counts         = $this->get_request_counts();
    1184         $total_requests = absint( array_sum( (array) $counts ) );
    1185 
    1186         $current_link_attributes = empty( $current_status ) ? ' class="current" aria-current="page"' : '';
    1187         $status_label            = sprintf(
    1188             /* translators: %s: all requests count */
    1189             _nx(
    1190                 'All <span class="count">(%s)</span>',
    1191                 'All <span class="count">(%s)</span>',
    1192                 $total_requests,
    1193                 'requests'
    1194             ),
    1195             number_format_i18n( $total_requests )
    1196         );
    1197 
    1198         $views['all'] = sprintf(
    1199             '<a href="%s"%s>%s</a>',
    1200             esc_url( $admin_url ),
    1201             $current_link_attributes,
    1202             $status_label
    1203         );
    1204 
    1205         foreach ( $statuses as $status => $label ) {
    1206             $post_status = get_post_status_object( $status );
    1207             if ( ! $post_status ) {
    1208                 continue;
    1209             }
    1210 
    1211             $current_link_attributes = $status === $current_status ? ' class="current" aria-current="page"' : '';
    1212             $total_status_requests   = absint( $counts->{$status} );
    1213             $status_label            = sprintf(
    1214                 translate_nooped_plural( $post_status->label_count, $total_status_requests ),
    1215                 number_format_i18n( $total_status_requests )
    1216             );
    1217             $status_link             = add_query_arg( 'filter-status', $status, $admin_url );
    1218 
    1219             $views[ $status ] = sprintf(
    1220                 '<a href="%s"%s>%s</a>',
    1221                 esc_url( $status_link ),
    1222                 $current_link_attributes,
    1223                 $status_label
    1224             );
    1225         }
    1226 
    1227         return $views;
    1228     }
    1229 
    1230     /**
    1231      * Get bulk actions.
    1232      *
    1233      * @since 4.9.6
    1234      *
    1235      * @return array List of bulk actions.
    1236      */
    1237     protected function get_bulk_actions() {
    1238         return array(
    1239             'delete' => __( 'Remove' ),
    1240             'resend' => __( 'Resend email' ),
    1241         );
    1242     }
    1243 
    1244     /**
    1245      * Process bulk actions.
    1246      *
    1247      * @since 4.9.6
    1248      */
    1249     public function process_bulk_action() {
    1250         $action      = $this->current_action();
    1251         $request_ids = isset( $_REQUEST['request_id'] ) ? wp_parse_id_list( wp_unslash( $_REQUEST['request_id'] ) ) : array();
    1252 
    1253         $count = 0;
    1254 
    1255         if ( $request_ids ) {
    1256             check_admin_referer( 'bulk-privacy_requests' );
    1257         }
    1258 
    1259         switch ( $action ) {
    1260             case 'delete':
    1261                 foreach ( $request_ids as $request_id ) {
    1262                     if ( wp_delete_post( $request_id, true ) ) {
    1263                         $count ++;
    1264                     }
    1265                 }
    1266 
    1267                 add_settings_error(
    1268                     'bulk_action',
    1269                     'bulk_action',
    1270                     /* translators: %d: number of requests */
    1271                     sprintf( _n( 'Deleted %d request', 'Deleted %d requests', $count ), $count ),
    1272                     'updated'
    1273                 );
    1274                 break;
    1275             case 'resend':
    1276                 foreach ( $request_ids as $request_id ) {
    1277                     $resend = _wp_privacy_resend_request( $request_id );
    1278 
    1279                     if ( $resend && ! is_wp_error( $resend ) ) {
    1280                         $count++;
    1281                     }
    1282                 }
    1283 
    1284                 add_settings_error(
    1285                     'bulk_action',
    1286                     'bulk_action',
    1287                     /* translators: %d: number of requests */
    1288                     sprintf( _n( 'Re-sent %d request', 'Re-sent %d requests', $count ), $count ),
    1289                     'updated'
    1290                 );
    1291                 break;
    1292         }
    1293     }
    1294 
    1295     /**
    1296      * Prepare items to output.
    1297      *
    1298      * @since 4.9.6
    1299      * @since 5.1.0 Added support for column sorting.
    1300      */
    1301     public function prepare_items() {
    1302         global $wpdb;
    1303 
    1304         $this->items    = array();
    1305         $posts_per_page = $this->get_items_per_page( $this->request_type . '_requests_per_page' );
    1306         $args           = array(
    1307             'post_type'      => $this->post_type,
    1308             'post_name__in'  => array( $this->request_type ),
    1309             'posts_per_page' => $posts_per_page,
    1310             'offset'         => isset( $_REQUEST['paged'] ) ? max( 0, absint( $_REQUEST['paged'] ) - 1 ) * $posts_per_page : 0,
    1311             'post_status'    => 'any',
    1312             's'              => isset( $_REQUEST['s'] ) ? sanitize_text_field( $_REQUEST['s'] ) : '',
    1313         );
    1314 
    1315         $orderby_mapping = array(
    1316             'requester' => 'post_title',
    1317             'requested' => 'post_date',
    1318         );
    1319 
    1320         if ( isset( $_REQUEST['orderby'] ) && isset( $orderby_mapping[ $_REQUEST['orderby'] ] ) ) {
    1321             $args['orderby'] = $orderby_mapping[ $_REQUEST['orderby'] ];
    1322         }
    1323 
    1324         if ( isset( $_REQUEST['order'] ) && in_array( strtoupper( $_REQUEST['order'] ), array( 'ASC', 'DESC' ), true ) ) {
    1325             $args['order'] = strtoupper( $_REQUEST['order'] );
    1326         }
    1327 
    1328         if ( ! empty( $_REQUEST['filter-status'] ) ) {
    1329             $filter_status       = isset( $_REQUEST['filter-status'] ) ? sanitize_text_field( $_REQUEST['filter-status'] ) : '';
    1330             $args['post_status'] = $filter_status;
    1331         }
    1332 
    1333         $requests_query = new WP_Query( $args );
    1334         $requests       = $requests_query->posts;
    1335 
    1336         foreach ( $requests as $request ) {
    1337             $this->items[] = wp_get_user_request_data( $request->ID );
    1338         }
    1339 
    1340         $this->items = array_filter( $this->items );
    1341 
    1342         $this->set_pagination_args(
    1343             array(
    1344                 'total_items' => $requests_query->found_posts,
    1345                 'per_page'    => $posts_per_page,
    1346             )
    1347         );
    1348     }
    1349 
    1350     /**
    1351      * Checkbox column.
    1352      *
    1353      * @since 4.9.6
    1354      *
    1355      * @param WP_User_Request $item Item being shown.
    1356      * @return string Checkbox column markup.
    1357      */
    1358     public function column_cb( $item ) {
    1359         return sprintf( '<input type="checkbox" name="request_id[]" value="%1$s" /><span class="spinner"></span>', esc_attr( $item->ID ) );
    1360     }
    1361 
    1362     /**
    1363      * Status column.
    1364      *
    1365      * @since 4.9.6
    1366      *
    1367      * @param WP_User_Request $item Item being shown.
    1368      * @return string Status column markup.
    1369      */
    1370     public function column_status( $item ) {
    1371         $status        = get_post_status( $item->ID );
    1372         $status_object = get_post_status_object( $status );
    1373 
    1374         if ( ! $status_object || empty( $status_object->label ) ) {
    1375             return '-';
    1376         }
    1377 
    1378         $timestamp = false;
    1379 
    1380         switch ( $status ) {
    1381             case 'request-confirmed':
    1382                 $timestamp = $item->confirmed_timestamp;
    1383                 break;
    1384             case 'request-completed':
    1385                 $timestamp = $item->completed_timestamp;
    1386                 break;
    1387         }
    1388 
    1389         echo '<span class="status-label status-' . esc_attr( $status ) . '">';
    1390         echo esc_html( $status_object->label );
    1391 
    1392         if ( $timestamp ) {
    1393             echo ' (' . $this->get_timestamp_as_date( $timestamp ) . ')';
    1394         }
    1395 
    1396         echo '</span>';
    1397     }
    1398 
    1399     /**
    1400      * Convert timestamp for display.
    1401      *
    1402      * @since 4.9.6
    1403      *
    1404      * @param int $timestamp Event timestamp.
    1405      * @return string Human readable date.
    1406      */
    1407     protected function get_timestamp_as_date( $timestamp ) {
    1408         if ( empty( $timestamp ) ) {
    1409             return '';
    1410         }
    1411 
    1412         $time_diff = time() - $timestamp;
    1413 
    1414         if ( $time_diff >= 0 && $time_diff < DAY_IN_SECONDS ) {
    1415             /* translators: human readable timestamp */
    1416             return sprintf( __( '%s ago' ), human_time_diff( $timestamp ) );
    1417         }
    1418 
    1419         return date_i18n( get_option( 'date_format' ), $timestamp );
    1420     }
    1421 
    1422     /**
    1423      * Default column handler.
    1424      *
    1425      * @since 4.9.6
    1426      *
    1427      * @param WP_User_Request $item        Item being shown.
    1428      * @param string          $column_name Name of column being shown.
    1429      * @return string Default column output.
    1430      */
    1431     public function column_default( $item, $column_name ) {
    1432         $cell_value = $item->$column_name;
    1433 
    1434         if ( in_array( $column_name, array( 'created_timestamp' ), true ) ) {
    1435             return $this->get_timestamp_as_date( $cell_value );
    1436         }
    1437 
    1438         return $cell_value;
    1439     }
    1440 
    1441     /**
    1442      * Actions column. Overridden by children.
    1443      *
    1444      * @since 4.9.6
    1445      *
    1446      * @param WP_User_Request $item Item being shown.
    1447      * @return string Email column markup.
    1448      */
    1449     public function column_email( $item ) {
    1450         return sprintf( '<a href="%1$s">%2$s</a> %3$s', esc_url( 'mailto:' . $item->email ), $item->email, $this->row_actions( array() ) );
    1451     }
    1452 
    1453     /**
    1454      * Next steps column. Overridden by children.
    1455      *
    1456      * @since 4.9.6
    1457      *
    1458      * @param WP_User_Request $item Item being shown.
    1459      */
    1460     public function column_next_steps( $item ) {}
    1461 
    1462     /**
    1463      * Generates content for a single row of the table,
    1464      *
    1465      * @since 4.9.6
    1466      *
    1467      * @param WP_User_Request $item The current item.
    1468      */
    1469     public function single_row( $item ) {
    1470         $status = $item->status;
    1471 
    1472         echo '<tr id="request-' . esc_attr( $item->ID ) . '" class="status-' . esc_attr( $status ) . '">';
    1473         $this->single_row_columns( $item );
    1474         echo '</tr>';
    1475     }
    1476 
    1477     /**
    1478      * Embed scripts used to perform actions. Overridden by children.
    1479      *
    1480      * @since 4.9.6
    1481      */
    1482     public function embed_scripts() {}
    1483 }
    1484 
    1485 /**
    1486  * WP_Privacy_Data_Export_Requests_Table class.
    1487  *
    1488  * @since 4.9.6
    1489  */
    1490 class WP_Privacy_Data_Export_Requests_Table extends WP_Privacy_Requests_Table {
    1491     /**
    1492      * Action name for the requests this table will work with.
    1493      *
    1494      * @since 4.9.6
    1495      *
    1496      * @var string $request_type Name of action.
    1497      */
    1498     protected $request_type = 'export_personal_data';
    1499 
    1500     /**
    1501      * Post type for the requests.
    1502      *
    1503      * @since 4.9.6
    1504      *
    1505      * @var string $post_type The post type.
    1506      */
    1507     protected $post_type = 'user_request';
    1508 
    1509     /**
    1510      * Actions column.
    1511      *
    1512      * @since 4.9.6
    1513      *
    1514      * @param WP_User_Request $item Item being shown.
    1515      * @return string Email column markup.
    1516      */
    1517     public function column_email( $item ) {
    1518         /** This filter is documented in wp-admin/includes/ajax-actions.php */
    1519         $exporters       = apply_filters( 'wp_privacy_personal_data_exporters', array() );
    1520         $exporters_count = count( $exporters );
    1521         $request_id      = $item->ID;
    1522         $nonce           = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id );
    1523 
    1524         $download_data_markup = '<div class="export-personal-data" ' .
    1525             'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' .
    1526             'data-request-id="' . esc_attr( $request_id ) . '" ' .
    1527             'data-nonce="' . esc_attr( $nonce ) .
    1528             '">';
    1529 
    1530         $download_data_markup .= '<span class="export-personal-data-idle"><button type="button" class="button-link export-personal-data-handle">' . __( 'Download Personal Data' ) . '</button></span>' .
    1531             '<span style="display:none" class="export-personal-data-processing" >' . __( 'Downloading Data...' ) . '</span>' .
    1532             '<span style="display:none" class="export-personal-data-success"><button type="button" class="button-link export-personal-data-handle">' . __( 'Download Personal Data Again' ) . '</button></span>' .
    1533             '<span style="display:none" class="export-personal-data-failed">' . __( 'Download failed.' ) . ' <button type="button" class="button-link">' . __( 'Retry' ) . '</button></span>';
    1534 
    1535         $download_data_markup .= '</div>';
    1536 
    1537         $row_actions = array(
    1538             'download-data' => $download_data_markup,
    1539         );
    1540 
    1541         return sprintf( '<a href="%1$s">%2$s</a> %3$s', esc_url( 'mailto:' . $item->email ), $item->email, $this->row_actions( $row_actions ) );
    1542     }
    1543 
    1544     /**
    1545      * Displays the next steps column.
    1546      *
    1547      * @since 4.9.6
    1548      *
    1549      * @param WP_User_Request $item Item being shown.
    1550      */
    1551     public function column_next_steps( $item ) {
    1552         $status = $item->status;
    1553 
    1554         switch ( $status ) {
    1555             case 'request-pending':
    1556                 esc_html_e( 'Waiting for confirmation' );
    1557                 break;
    1558             case 'request-confirmed':
    1559                 /** This filter is documented in wp-admin/includes/ajax-actions.php */
    1560                 $exporters       = apply_filters( 'wp_privacy_personal_data_exporters', array() );
    1561                 $exporters_count = count( $exporters );
    1562                 $request_id      = $item->ID;
    1563                 $nonce           = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id );
    1564 
    1565                 echo '<div class="export-personal-data" ' .
    1566                     'data-send-as-email="1" ' .
    1567                     'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' .
    1568                     'data-request-id="' . esc_attr( $request_id ) . '" ' .
    1569                     'data-nonce="' . esc_attr( $nonce ) .
    1570                     '">';
    1571 
    1572                 ?>
    1573                 <span class="export-personal-data-idle"><button type="button" class="button export-personal-data-handle"><?php _e( 'Send Export Link' ); ?></button></span>
    1574                 <span style="display:none" class="export-personal-data-processing button updating-message" ><?php _e( 'Sending Email...' ); ?></span>
    1575                 <span style="display:none" class="export-personal-data-success success-message" ><?php _e( 'Email sent.' ); ?></span>
    1576                 <span style="display:none" class="export-personal-data-failed"><?php _e( 'Email could not be sent.' ); ?> <button type="button" class="button export-personal-data-handle"><?php _e( 'Retry' ); ?></button></span>
    1577                 <?php
    1578 
    1579                 echo '</div>';
    1580                 break;
    1581             case 'request-failed':
    1582                 submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item->ID . ']', false );
    1583                 break;
    1584             case 'request-completed':
    1585                 echo '<a href="' . esc_url(
    1586                     wp_nonce_url(
    1587                         add_query_arg(
    1588                             array(
    1589                                 'action'     => 'delete',
    1590                                 'request_id' => array( $item->ID ),
    1591                             ),
    1592                             admin_url( 'tools.php?page=export_personal_data' )
    1593                         ),
    1594                         'bulk-privacy_requests'
    1595                     )
    1596                 ) . '" class="button">' . esc_html__( 'Remove request' ) . '</a>';
    1597                 break;
    1598         }
    1599     }
    1600 }
    1601 
    1602 /**
    1603  * WP_Privacy_Data_Removal_Requests_Table class.
    1604  *
    1605  * @since 4.9.6
    1606  */
    1607 class WP_Privacy_Data_Removal_Requests_Table extends WP_Privacy_Requests_Table {
    1608     /**
    1609      * Action name for the requests this table will work with.
    1610      *
    1611      * @since 4.9.6
    1612      *
    1613      * @var string $request_type Name of action.
    1614      */
    1615     protected $request_type = 'remove_personal_data';
    1616 
    1617     /**
    1618      * Post type for the requests.
    1619      *
    1620      * @since 4.9.6
    1621      *
    1622      * @var string $post_type The post type.
    1623      */
    1624     protected $post_type = 'user_request';
    1625 
    1626     /**
    1627      * Actions column.
    1628      *
    1629      * @since 4.9.6
    1630      *
    1631      * @param WP_User_Request $item Item being shown.
    1632      * @return string Email column markup.
    1633      */
    1634     public function column_email( $item ) {
    1635         $row_actions = array();
    1636 
    1637         // Allow the administrator to "force remove" the personal data even if confirmation has not yet been received.
    1638         $status = $item->status;
    1639         if ( 'request-confirmed' !== $status ) {
    1640             /** This filter is documented in wp-admin/includes/ajax-actions.php */
    1641             $erasers       = apply_filters( 'wp_privacy_personal_data_erasers', array() );
    1642             $erasers_count = count( $erasers );
    1643             $request_id    = $item->ID;
    1644             $nonce         = wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id );
    1645 
    1646             $remove_data_markup = '<div class="remove-personal-data force-remove-personal-data" ' .
    1647                 'data-erasers-count="' . esc_attr( $erasers_count ) . '" ' .
    1648                 'data-request-id="' . esc_attr( $request_id ) . '" ' .
    1649                 'data-nonce="' . esc_attr( $nonce ) .
    1650                 '">';
    1651 
    1652             $remove_data_markup .= '<span class="remove-personal-data-idle"><button type="button" class="button-link remove-personal-data-handle">' . __( 'Force Erase Personal Data' ) . '</button></span>' .
    1653                 '<span style="display:none" class="remove-personal-data-processing" >' . __( 'Erasing Data...' ) . '</span>' .
    1654                 '<span style="display:none" class="remove-personal-data-failed">' . __( 'Force Erase has failed.' ) . ' <button type="button" class="button-link remove-personal-data-handle">' . __( 'Retry' ) . '</button></span>';
    1655 
    1656             $remove_data_markup .= '</div>';
    1657 
    1658             $row_actions = array(
    1659                 'remove-data' => $remove_data_markup,
    1660             );
    1661         }
    1662 
    1663         return sprintf( '<a href="%1$s">%2$s</a> %3$s', esc_url( 'mailto:' . $item->email ), $item->email, $this->row_actions( $row_actions ) );
    1664     }
    1665 
    1666     /**
    1667      * Next steps column.
    1668      *
    1669      * @since 4.9.6
    1670      *
    1671      * @param WP_User_Request $item Item being shown.
    1672      */
    1673     public function column_next_steps( $item ) {
    1674         $status = $item->status;
    1675 
    1676         switch ( $status ) {
    1677             case 'request-pending':
    1678                 esc_html_e( 'Waiting for confirmation' );
    1679                 break;
    1680             case 'request-confirmed':
    1681                 /** This filter is documented in wp-admin/includes/ajax-actions.php */
    1682                 $erasers       = apply_filters( 'wp_privacy_personal_data_erasers', array() );
    1683                 $erasers_count = count( $erasers );
    1684                 $request_id    = $item->ID;
    1685                 $nonce         = wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id );
    1686 
    1687                 echo '<div class="remove-personal-data" ' .
    1688                     'data-force-erase="1" ' .
    1689                     'data-erasers-count="' . esc_attr( $erasers_count ) . '" ' .
    1690                     'data-request-id="' . esc_attr( $request_id ) . '" ' .
    1691                     'data-nonce="' . esc_attr( $nonce ) .
    1692                     '">';
    1693 
    1694                 ?>
    1695                 <span class="remove-personal-data-idle"><button type="button" class="button remove-personal-data-handle"><?php _e( 'Erase Personal Data' ); ?></button></span>
    1696                 <span style="display:none" class="remove-personal-data-processing button updating-message" ><?php _e( 'Erasing Data...' ); ?></span>
    1697                 <span style="display:none" class="remove-personal-data-failed"><?php _e( 'Erasing Data has failed.' ); ?> <button type="button" class="button remove-personal-data-handle"><?php _e( 'Retry' ); ?></button></span>
    1698                 <?php
    1699 
    1700                 echo '</div>';
    1701 
    1702                 break;
    1703             case 'request-failed':
    1704                 submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item->ID . ']', false );
    1705                 break;
    1706             case 'request-completed':
    1707                 echo '<a href="' . esc_url(
    1708                     wp_nonce_url(
    1709                         add_query_arg(
    1710                             array(
    1711                                 'action'     => 'delete',
    1712                                 'request_id' => array( $item->ID ),
    1713                             ),
    1714                             admin_url( 'tools.php?page=remove_personal_data' )
    1715                         ),
    1716                         'bulk-privacy_requests'
    1717                     )
    1718                 ) . '" class="button">' . esc_html__( 'Remove request' ) . '</a>';
    1719                 break;
    1720         }
    1721     }
    1722 
    1723 }
Note: See TracChangeset for help on using the changeset viewer.