Make WordPress Core

Ticket #43438: 43438.diff

File 43438.diff, 15.8 KB (added by allendav, 7 years ago)

First pass at an extensible personal data exporter - includes core comments personal data export

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

     
    129129        'get-post-thumbnail-html',
    130130        'get-community-events',
    131131        'edit-theme-plugin-file',
     132        'wp-privacy-export-personal-data',
    132133);
    133134
    134135// Deprecated
  • src/wp-admin/includes/ajax-actions.php

     
    43264326                );
    43274327        }
    43284328}
     4329
     4330function wp_ajax_wp_privacy_export_personal_data() {
     4331        $email_address = $_POST['email'];
     4332        $exporter_index = $_POST['exporter'];
     4333        $page = $_POST['page'];
     4334
     4335        $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() );
     4336
     4337        if ( ! is_email( $email_address ) ) {
     4338                wp_send_json_error( 'a valid email address must be given' );
     4339        }
     4340
     4341        if ( $exporter_index < 0 ) {
     4342                wp_send_json_error( 'exporter index cannot be negative' );
     4343        }
     4344
     4345        if ( $exporter_index > count( $exporters ) - 1 ) {
     4346                wp_send_json_error( 'exporter index out of range' );
     4347        }
     4348
     4349        if ( $page < 0 ) {
     4350                wp_send_json_error( 'page index cannot be negative' );
     4351        }
     4352
     4353        $response = call_user_func( $exporters[ $exporter_index ]['callback'], $email_address, $page );
     4354
     4355        wp_send_json_success( $response );
     4356}
  • src/wp-admin/menu.php

     
    256256        $submenu['tools.php'][5]  = array( __( 'Available Tools' ), 'edit_posts', 'tools.php' );
    257257        $submenu['tools.php'][10] = array( __( 'Import' ), 'import', 'import.php' );
    258258        $submenu['tools.php'][15] = array( __( 'Export' ), 'export', 'export.php' );
     259        $submenu['tools.php'][20] = array( __( 'Privacy' ), 'manage_options', 'tools-privacy.php' );
    259260if ( is_multisite() && ! is_main_site() ) {
    260261        $submenu['tools.php'][25] = array( __( 'Delete Site' ), 'delete_site', 'ms-delete-site.php' );
    261262}
  • src/wp-admin/tools-privacy.php

     
     1<?php
     2/**
     3 * Privacy Policy Screen.
     4 *
     5 * @package WordPress
     6 * @subpackage Administration
     7 */
     8
     9/** WordPress Administration Bootstrap */
     10require_once( dirname( __FILE__ ) . '/admin.php' );
     11
     12if ( ! current_user_can( 'manage_options' ) ) {
     13        wp_die( __( 'Sorry, you are not allowed to manage privacy on this site.' ) );
     14}
     15
     16$title = __( 'Privacy Tools' );
     17
     18$action = isset( $_GET['action'] ) ? $_GET['action'] : '';
     19
     20$doing_personal_data_export_for_email = '';
     21
     22if ( ! empty( $action ) ) {
     23        check_admin_referer( $action );
     24
     25        if ( 'set-privacy-page' === $action ) {
     26                $privacy_policy_page_id = isset( $_POST['page_for_privacy_policy'] ) ? $_POST['page_for_privacy_policy'] : 0;
     27                update_option( 'page_for_privacy_policy', $privacy_policy_page_id );
     28                add_settings_error(
     29                        'page_for_privacy_policy',
     30                        'page_for_privacy_policy',
     31                        __( 'Privacy policy page updated successfully' ),
     32                        'updated'
     33                );
     34        }
     35
     36        if ( 'create-privacy-page' === $action ) {
     37                $privacy_policy_page_id = wp_insert_post(
     38                        array(
     39                                'post_title'  => __( 'Privacy Policy' ),
     40                                'post_status' => 'publish',
     41                                'post_type'   => 'page',
     42                        ),
     43                        true
     44                );
     45
     46                if ( is_wp_error( $privacy_policy_page_id ) ) {
     47                        add_settings_error(
     48                                'page_for_privacy_policy',
     49                                'page_for_privacy_policy',
     50                                __( 'Unable to create privacy policy page' ),
     51                                'error'
     52                        );
     53                } else {
     54                        update_option( 'page_for_privacy_policy', $privacy_policy_page_id );
     55                        add_settings_error(
     56                                'page_for_privacy_policy',
     57                                'page_for_privacy_policy',
     58                                __( 'Privacy policy page created successfully' ),
     59                                'updated'
     60                        );
     61                }
     62        }
     63
     64        if ( 'export-personal-data' === $action ) {
     65
     66                $username_or_email_address = isset( $_POST['username_or_email_to_export'] ) ? $_POST['username_or_email_to_export'] : '';
     67                $username_or_email_address = sanitize_text_field( $username_or_email_address );
     68
     69                if ( ! is_email( $username_or_email_address ) ) {
     70                        $user = get_user_by( 'login', $username_or_email_address );
     71                        if ( ! $user instanceof WP_User ) {
     72                                add_settings_error(
     73                                        'username_or_email_to_export',
     74                                        'username_or_email_to_export',
     75                                        __( 'Unable to export personal data. Username not recognized.' ),
     76                                        'error'
     77                                );
     78                        } else {
     79                                $doing_personal_data_export_for_email = $user->user_email;
     80                        }
     81                } else {
     82                        $doing_personal_data_export_for_email = $username_or_email_address;
     83                }
     84
     85                if ( ! empty( $doing_personal_data_export_for_email ) ) {
     86                        $title = __( 'Export Personal Data' );
     87                }
     88        }
     89}
     90
     91if ( empty( $doing_personal_data_export_for_email ) ) {
     92        // If a privacy policy page ID is available, make sure the page actually exists. If not, display a warning
     93        $privacy_policy_page_exists = false;
     94        $privacy_policy_page_id = get_option( 'page_for_privacy_policy' );
     95        if ( ! empty( $privacy_policy_page_id ) ) {
     96                        $privacy_policy_page = get_post( $privacy_policy_page_id );
     97                        if ( ! $privacy_policy_page instanceof WP_Post ) {
     98                                add_settings_error(
     99                                        'page_for_privacy_policy',
     100                                        'page_for_privacy_policy',
     101                                        __( 'The currently selected privacy policy page does not exist' ),
     102                                        'warning'
     103                                );
     104                        } else {
     105                                if ( 'trash' === $privacy_policy_page->post_status ) {
     106                                        add_settings_error(
     107                                                'page_for_privacy_policy',
     108                                                'page_for_privacy_policy',
     109                                                __( 'The currently selected privacy policy page is in the trash. Please select a new privacy policy page.' ),
     110                                                'error'
     111                                        );
     112                                } else {
     113                                        $privacy_policy_page_exists = true;
     114                                }
     115                        }
     116        }
     117
     118        get_current_screen()->add_help_tab( array(
     119                'id'      => 'privacy',
     120                'title'   => __( 'Privacy' ),
     121                'content' => '<p>' . __( 'This page provides tools with which you can manage your user\'s personal data and site\'s privacy policy.' ) . '</p>',
     122        ) );
     123
     124        get_current_screen()->set_help_sidebar(
     125                '<p><strong>' . __( 'For more information:' ) . '</strong></p>' .
     126                '<p>' . __( '<a href="#">Documentation on privacy</a>' ) . '</p>'
     127        );
     128}
     129
     130require_once( ABSPATH . 'wp-admin/admin-header.php' );
     131
     132?>
     133<div class="wrap">
     134        <h1><?php echo esc_html( $title ); ?></h1>
     135        <?php settings_errors(); ?>
     136<?php
     137
     138// Run the exporter
     139if ( ! empty( $doing_personal_data_export_for_email ) ) {
     140        if ( $username_or_email_address === $doing_personal_data_export_for_email ) {
     141                $heading = sprintf(
     142                        __( 'Exporting personal data for %s' ),
     143                        $doing_personal_data_export_for_email
     144                );
     145        } else {
     146                $heading = sprintf(
     147                        __( 'Exporting personal data for %1$s (%2$s)' ),
     148                        $doing_personal_data_export_for_email,
     149                        $username_or_email_address
     150                );
     151        }
     152        ?>
     153        <p>
     154                <?php echo esc_html( $heading ); ?>
     155        </p>
     156
     157        <div id="exporters-container">
     158                <ul id="exporter-list">
     159                </ul>
     160                <table class="wp-list-table widefat striped" id="exported-data-table">
     161                        <tr>
     162                                <th><?php _e( 'Name' ); ?></th>
     163                                <th><?php _e( 'Value' ); ?></th>
     164                        </tr>
     165                </table>
     166        </div>
     167
     168        <?php
     169        // Build the array of exporters and emit it as an array accessible to javascript along with a nonce
     170        $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() );
     171
     172        $exporter_names = array();
     173        foreach ( ( array ) $exporters as $exporter ) {
     174                $exporter_names[] = $exporter['exporter_friendly_name'];
     175        }
     176
     177        ?>
     178
     179        <!-- scripts for the exporter -->
     180
     181        <script>
     182                ( function( $ ) {
     183                        $( document ).ready( function() {
     184                                var exportForEmail = <?php echo json_encode( $doing_personal_data_export_for_email ); ?>;
     185                                var exporterNames = <?php echo json_encode( $exporter_names ); ?>;
     186                                var successMessage = "<?php echo esc_attr( __( 'Export completed successfully' ) ); ?>";
     187                                var failureMessage = "<?php echo esc_attr( __( 'A failure occurred during export' ) ); ?>";
     188
     189                                function on_exports_done_success() {
     190                                        $( '#exporters-container' ).append( '<p>' + successMessage + '</p>' );
     191                                }
     192
     193                                function on_export_failure( textStatus, error ) {
     194                                        $( '#exporters-container' ).append( '<p>' + failureMessage + '</p>' );
     195                                        $( '#exporters-container' ).append( '<p>' + textStatus + '</p>' );
     196                                }
     197
     198                                function do_next_export( exporterIndex, pageIndex ) {
     199                                        $.ajax( {
     200                                                url: ajaxurl,
     201                                                data: {
     202                                                        action: 'wp-privacy-export-personal-data',
     203                                                        email: exportForEmail,
     204                                                        exporter: exporterIndex,
     205                                                        page: pageIndex
     206                                                },
     207                                                method: 'post'
     208                                        } ).done( function( response ) {
     209                                                var responseData = response.data;
     210                                                for ( var dataIndex = 0; dataIndex < responseData.data.length; dataIndex++ ) {
     211                                                        $( '#exported-data-table' ).append( '<tr><td>' + responseData.data[ dataIndex ].name + '</td><td>' + responseData.data[ dataIndex ].value + '</td></tr>' );
     212                                                }
     213                                                if ( ! responseData.done ) {
     214                                                        setTimeout( do_next_export( exporterIndex, pageIndex + 1 ) );
     215                                                } else {
     216                                                        if ( exporterIndex < exporterNames.length - 1 ) {
     217                                                                setTimeout( do_next_export( exporterIndex + 1, 0 ) );
     218                                                        } else {
     219                                                                on_exports_done_success();
     220                                                        }
     221                                                }
     222                                        } ).fail( function( jqxhr, textStatus, error ) {
     223                                                on_export_failure( textStatus, error );
     224                                        } );
     225                                }
     226
     227                                // And now, let's begin
     228                                do_next_export( 0, 0 );
     229                        } )
     230                } ) ( jQuery );
     231        </script>
     232
     233        <p>
     234                <a href="tools-privacy.php"><?php esc_html_e( 'Return to privacy tools' ); ?></a>
     235        </p>
     236        <?php
     237}
     238
     239// Display the privacy tools form table
     240if ( empty( $doing_personal_data_export_for_email ) ) {
     241        ?>
     242        <table class="form-table">
     243                <tr>
     244                        <th scope="row"><?php _e( 'Privacy policy page' ); ?></th>
     245                        <td id="privacy-page-form-container">
     246                                <form method="post" action="tools-privacy.php?action=set-privacy-page">
     247                                        <?php wp_nonce_field( 'set-privacy-page' ); ?>
     248                                        <fieldset>
     249                                                <legend class="screen-reader-text"><span><?php _e( 'Privacy policy page' ); ?></span></legend>
     250                                                <label for="page_for_privacy_policy">
     251                                                        <?php wp_dropdown_pages(
     252                                                                array(
     253                                                                        'name'              => 'page_for_privacy_policy',
     254                                                                        'show_option_none'  => __( '&mdash; Select &mdash;' ),
     255                                                                        'option_none_value' => '0',
     256                                                                        'selected'          => get_option( 'page_for_privacy_policy' ),
     257                                                                )
     258                                                        );
     259                                                        ?>
     260                                                </label>
     261                                        </fieldset>
     262                                        <?php submit_button( __( 'Set Page' ) ); ?>
     263                                </form>
     264                                <?php
     265                                        if ( empty( $privacy_policy_page_id ) ) {
     266                                                ?>
     267                                                <form method="post" action="tools-privacy.php?action=create-privacy-page">
     268                                                        <?php wp_nonce_field( 'create-privacy-page' ); ?>
     269                                                        <p>
     270                                                                <?php _e( 'If you don\'t already have a page for your privacy policy, you can create one now' ); ?>
     271                                                        </p>
     272                                                        <?php submit_button( __( 'Create Page' ) ); ?>
     273                                                </form>
     274                                                <?php
     275                                        }
     276                                        if( $privacy_policy_page_exists ) {
     277                                                $edit_href = add_query_arg(
     278                                                        array(
     279                                                                'post'  => $privacy_policy_page_id,
     280                                                                'action' => 'edit',
     281                                                        ),
     282                                                        admin_url( 'post.php' )
     283                                                );
     284                                                $view_href = get_permalink( $privacy_policy_page_id );
     285                                                $prompt = sprintf(
     286                                                        __( 'You may also <a href="%1$s">edit</a> or <a href="%2$s">view</a> your privacy policy page' ),
     287                                                        $edit_href,
     288                                                        $view_href
     289                                                );
     290                                                $allowed_html = array( 'a' => array( 'href' => array() ) );
     291                                                ?>
     292                                                <p class="description" id="page_for_privacy_policy_view_edit">
     293                                                        <?php echo wp_kses( $prompt, $allowed_html ); ?>
     294                                                </p>
     295                                                <?php
     296                                        }
     297                                ?>
     298                        </td>
     299                </tr>
     300                <tr>
     301                        <th scope="row"><?php _e( 'Export personal data' ); ?></th>
     302                        <td id="export-personal-data-form-container">
     303                                <form method="post" action="tools-privacy.php?action=export-personal-data">
     304                                        <?php wp_nonce_field( 'export-personal-data' ); ?>
     305                                        <fieldset>
     306                                                <legend class="screen-reader-text"><span><?php _e( 'Enter the username or email address of the user whose personal data you wish to export' ); ?></span></legend>
     307                                                <label for="username_or_email_to_export">
     308                                                        <input type="text" class="regular-text" name="username_or_email_to_export" />
     309                                                </label>
     310                                                <p class="description"><?php _e( 'The username or email address of the user whose personal data you wish to export' ); ?></p>
     311                                        </fieldset>
     312                                        <?php submit_button( __( 'Begin Export' ) ); ?>
     313                                </form>
     314                        </td>
     315                </tr>
     316        </table>
     317        <!-- scripts for the privacy form table content -->
     318        <script>
     319                ( function( $ ) {
     320                        $( document ).ready( function() {
     321                                $( '#page_for_privacy_policy' ).change( function() {
     322                                        $( '#page_for_privacy_policy_view_edit' ).hide();
     323                                } );
     324                        } );
     325                } ( jQuery ) );
     326        </script>
     327<?php
     328}
     329?>
     330
     331</div>
     332<?php
     333
     334include( ABSPATH . 'wp-admin/admin-footer.php' );
  • src/wp-includes/comment.php

     
    32743274
    32753275        return get_comment( $comment_id );
    32763276}
     3277
     3278/**
     3279 * Registers the personal data exporter for comments
     3280 *
     3281 * @param array   $exporters   An array of personal data exporters.
     3282 * @return array  An array of personal data exporters.
     3283 */
     3284function wp_register_comment_personal_data_exporter( $exporters ) {
     3285        $exporters[] = array(
     3286                'exporter_friendly_name' => __( 'WordPress Comments' ),
     3287                'callback' => 'wp_comments_personal_data_exporter'
     3288        );
     3289
     3290        return $exporters;
     3291}
     3292
     3293/**
     3294 * Finds and exports data which could be used to identify a person from comments assocated with an email address.
     3295 *
     3296 * @param string  $email  The comment author email address.
     3297 * @param int     $page   Comment page.
     3298 * @return array  An array of personal data in name value pairs
     3299 */
     3300function wp_comments_personal_data_exporter( $email_address, $page ) {
     3301
     3302        $personal_data = array();
     3303        $email_address = trim( strtolower( $email_address ) );
     3304
     3305        // TODO - actually use the page parameter to avoid fetching all the comments at once
     3306        // that will be a no-no on sites with lots of comments
     3307
     3308        // TODO - pass the email address to get_comments if possible to be most performant
     3309
     3310        $comments = get_comments(
     3311                array(
     3312                        'order_by' => 'comment_ID',
     3313                        'order' => 'ASC'
     3314                )
     3315        );
     3316
     3317        $comment_personal_data_props = array(
     3318                'comment_author'       => __( 'Comment Author' ),
     3319                'comment_author_email' => __( 'Comment Author Email' ),
     3320                'comment_author_url'   => __( 'Comment Author URL' ),
     3321                'comment_author_IP'    => __( 'Comment Author IP' ),
     3322                'comment_agent'        => __( 'Comment Agent' )
     3323        );
     3324
     3325        foreach ( (array) $comments as $comment ) {
     3326                $comment_author_email = trim( strtolower( $comment->comment_author_email ) );
     3327                if ( $comment_author_email === $email_address ) {
     3328                        foreach ( (array) $comment_personal_data_props as $key => $description ) {
     3329                                $value = $comment->$key;
     3330                                if ( ! empty( $value ) ) {
     3331                                        $name = sprintf(
     3332                                                __( 'Comment %d : %s' ),
     3333                                                $comment->comment_ID,
     3334                                                $description
     3335                                        );
     3336                                        $personal_data[] = array(
     3337                                                'name'  => $name,
     3338                                                'value' => $value
     3339                                        );
     3340                                }
     3341                        }
     3342                }
     3343        }
     3344
     3345        return array(
     3346                'data' => $personal_data,
     3347                'done' => true,
     3348        );
     3349}
  • src/wp-includes/default-filters.php

     
    328328add_action( 'do_pings', 'do_all_pings', 10, 1 );
    329329add_action( 'do_robots', 'do_robots' );
    330330add_action( 'set_comment_cookies', 'wp_set_comment_cookies', 10, 3 );
     331add_filter( 'wp_privacy_personal_data_exporters', 'wp_register_comment_personal_data_exporter', 10 );
    331332add_action( 'sanitize_comment_cookies', 'sanitize_comment_cookies' );
    332333add_action( 'admin_print_scripts', 'print_emoji_detection_script' );
    333334add_action( 'admin_print_scripts', 'print_head_scripts', 20 );