Make WordPress Core

Changeset 43012


Ignore:
Timestamp:
04/27/2018 07:53:37 PM (8 years ago)
Author:
azaozz
Message:

Privacy: add means to export personal data by username or email address. Generate a zipped export file containing all data. First run.

Props allendav.
See #43546.

Location:
trunk/src
Files:
8 edited

Legend:

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

    r43008 r43012  
    133133add_action( 'upgrader_process_complete', 'wp_update_themes', 10, 0 );
    134134
     135// Privacy hooks
     136add_filter( 'wp_privacy_personal_data_export_page', 'wp_privacy_process_personal_data_export_page', 10, 6 );
     137add_action( 'wp_privacy_personal_data_export_file', 'wp_privacy_generate_personal_data_export_file', 10 );
     138
    135139// Privacy policy text changes check.
    136140add_action( 'admin_init', array( 'WP_Privacy_Policy_Content', 'text_change_check' ), 20 );
     
    144148// Stop checking for text changes after the policy page is updated.
    145149add_action( 'post_updated', array( 'WP_Privacy_Policy_Content', '_policy_page_updated' ) );
    146 
  • trunk/src/wp-admin/includes/ajax-actions.php

    r43011 r43012  
    43284328}
    43294329
     4330/**
     4331 * Ajax handler for exporting a user's personal data.
     4332 *
     4333 * @since 4.9.6
     4334 */
    43304335function wp_ajax_wp_privacy_export_personal_data() {
    4331         check_ajax_referer( 'wp-privacy-export-personal-data', 'security' );
     4336        $request_id  = (int) $_POST['id'];
     4337
     4338        if ( empty( $request_id ) ) {
     4339                wp_send_json_error( __( 'Error: Invalid request ID.' ) );
     4340        }
    43324341
    43334342        if ( ! current_user_can( 'manage_options' ) ) {
     
    43354344        }
    43364345
    4337         $email_address  = sanitize_text_field( $_POST['email'] );
     4346        check_ajax_referer( 'wp-privacy-export-personal-data-' . $request_id, 'security' );
     4347
     4348        // Get the request data.
     4349        $request = wp_get_user_request_data( $request_id );
     4350
     4351        if ( ! $request || 'export_personal_data' !== $request->action_name ) {
     4352                wp_send_json_error( __( 'Error: Invalid request type.' ) );
     4353        }
     4354
     4355        $email_address = $request->email;
     4356        if ( ! is_email( $email_address ) ) {
     4357                wp_send_json_error( __( 'Error: A valid email address must be given.' ) );
     4358        }
     4359
    43384360        $exporter_index = (int) $_POST['exporter'];
    43394361        $page           = (int) $_POST['page'];
     4362        $send_as_email  = isset( $_POST['sendAsEmail'] ) ? ( "true" === $_POST['sendAsEmail'] ) : false;
    43404363
    43414364        /**
     
    43494372         *         callback               string  Callable exporter that accepts an email address and
    43504373         *                                        a page and returns an array of name => value
    4351          *                                        pairs of personal data
    4352          *         exporter_friendly_name string  Translated user facing friendly name for the exporter
     4374         *                                        pairs of personal data.
     4375         *         exporter_friendly_name string  Translated user facing friendly name for the exporter.
    43534376         *     ]
    43544377         * }
     
    43764399                }
    43774400
    4378                 // Surprisingly, email addresses can contain mutli-byte characters now
    4379                 $email_address = trim( mb_strtolower( $email_address ) );
    4380 
    4381                 if ( ! is_email( $email_address ) ) {
    4382                         wp_send_json_error( 'A valid email address must be given.' );
    4383                 }
    4384 
    43854401                $exporter = $exporters[ $index ];
     4402
    43864403                if ( ! is_array( $exporter ) ) {
    43874404                        wp_send_json_error( "Expected an array describing the exporter at index {$exporter_index}." );
    43884405                }
    4389                 if ( ! array_key_exists( 'callback', $exporter ) ) {
    4390                         wp_send_json_error( "Exporter array at index {$exporter_index} does not include a callback." );
    4391                 }
    4392                 if ( ! is_callable( $exporter['callback'] ) ) {
    4393                         wp_send_json_error( "Exporter callback at index {$exporter_index} is not a valid callback." );
    4394                 }
    43954406                if ( ! array_key_exists( 'exporter_friendly_name', $exporter ) ) {
    43964407                        wp_send_json_error( "Exporter array at index {$exporter_index} does not include a friendly name." );
     4408                }
     4409                if ( ! array_key_exists( 'callback', $exporter ) ) {
     4410                        wp_send_json_error( "Exporter does not include a callback: {$exporter['exporter_friendly_name']}." );
     4411                }
     4412                if ( ! is_callable( $exporter['callback'] ) ) {
     4413                        wp_send_json_error( "Exporter callback is not a valid callback: {$exporter['exporter_friendly_name']}." );
    43974414                }
    43984415
     
    44184435                }
    44194436        } else {
    4420                 // No exporters, so we're done
     4437                // No exporters, so we're done.
    44214438                $response = array(
    44224439                        'data' => array(),
     
    44364453         * @param string $email_address   The email address associated with this personal data.
    44374454         * @param int    $page            The zero-based page for this response.
     4455         * @param int    $request_id      The privacy request post ID associated with this request.
     4456         * @param bool   $send_as_email   Whether the final results of the export should be emailed to the user.
    44384457         */
    4439         $response = apply_filters( 'wp_privacy_personal_data_export_page', $response, $exporter_index, $email_address, $page );
     4458        $response = apply_filters( 'wp_privacy_personal_data_export_page', $response, $exporter_index, $email_address, $page, $request_id, $send_as_email );
     4459
    44404460        if ( is_wp_error( $response ) ) {
    44414461                wp_send_json_error( $response );
     
    44634483        check_ajax_referer( 'wp-privacy-erase-personal-data-' . $request_id, 'security' );
    44644484
    4465         // Find the request CPT
     4485        // Get the request data.
    44664486        $request = wp_get_user_request_data( $request_id );
    44674487
  • trunk/src/wp-admin/includes/file.php

    r42983 r43012  
    19351935        <?php
    19361936}
     1937
     1938/**
     1939 * Generate a single group for the personal data export report.
     1940 *
     1941 * @since 4.9.6
     1942 *
     1943 * @param array  $group_data {
     1944 *     The group data to render.
     1945 *
     1946 *     @type string $group_label  The user-facing heading for the group, e.g. 'Comments'.
     1947 *     @type array  $items        {
     1948 *         An array of group items.
     1949 *
     1950 *         @type array  $group_item_data  {
     1951 *             An array of name-value pairs for the item.
     1952 *
     1953 *             @type string $name   The user-facing name of an item name-value pair, e.g. 'IP Address'.
     1954 *             @type string $value  The user-facing value of an item data pair, e.g. '50.60.70.0'.
     1955 *         }
     1956 *     }
     1957 * }
     1958 * @return string The HTML for this group and its items.
     1959 */
     1960function wp_privacy_generate_personal_data_export_group_html( $group_data ) {
     1961        $allowed_tags      = array(
     1962                'a' => array(
     1963                        'href'   => array(),
     1964                        'target' => array()
     1965                ),
     1966                'br' => array()
     1967        );
     1968        $allowed_protocols = array( 'http', 'https' );
     1969        $group_html        = '';
     1970
     1971        $group_html .= '<h2>' . esc_html( $group_data['group_label'] ) . '</h2>';
     1972        $group_html .= '<div>';
     1973
     1974        foreach ( (array) $group_data['items'] as $group_item_id => $group_item_data ) {
     1975                $group_html .= '<table>';
     1976                $group_html .= '<tbody>';
     1977
     1978                foreach ( (array) $group_item_data as $group_item_datum ) {
     1979                        $group_html .= '<tr>';
     1980                        $group_html .= '<th>' . esc_html( $group_item_datum['name'] ) . '</th>';
     1981                        $group_html .= '<td>' . wp_kses( $group_item_datum['value'], $allowed_tags, $allowed_protocols ) . '</td>';
     1982                        $group_html .= '</tr>';
     1983                }
     1984
     1985                $group_html .= '</tbody>';
     1986                $group_html .= '</table>';
     1987        }
     1988
     1989        $group_html .= '</div>';
     1990
     1991        return $group_html;
     1992}
     1993
     1994/**
     1995 * Generate the personal data export file.
     1996 *
     1997 * @since 4.9.6
     1998 *
     1999 * @param int  $request_id  The export request ID.
     2000 */
     2001function wp_privacy_generate_personal_data_export_file( $request_id ) {
     2002        // Maybe make this a cron job instead.
     2003        wp_privacy_delete_old_export_files();
     2004
     2005        if ( ! class_exists( 'ZipArchive' ) ) {
     2006                wp_send_json_error( __( 'Unable to generate export file. ZipArchive not available.' ) );
     2007        }
     2008
     2009        // Get the request data.
     2010        $request = wp_get_user_request_data( $request_id );
     2011
     2012        if ( ! $request || 'export_personal_data' !== $request->action_name ) {
     2013                wp_send_json_error( __( 'Invalid request ID when generating export file' ) );
     2014        }
     2015
     2016        $email_address = $request->email;
     2017
     2018        if ( ! is_email( $email_address ) ) {
     2019                wp_send_json_error( __( 'Invalid email address when generating export file' ) );
     2020        }
     2021
     2022        // Create the exports folder if needed.
     2023        $upload_dir  = wp_upload_dir();
     2024        $exports_dir = trailingslashit( $upload_dir['basedir'] . '/exports' );
     2025        $exports_url = trailingslashit( $upload_dir['baseurl'] . '/exports' );
     2026
     2027        $result = wp_mkdir_p( $exports_dir );
     2028        if ( is_wp_error( $result ) ) {
     2029                wp_send_json_error( $result->get_error_message() );
     2030        }
     2031
     2032        // Protect export folder from browsing.
     2033        $index_pathname = $exports_dir . 'index.html';
     2034        if ( ! file_exists( $index_pathname ) ) {
     2035                $file = fopen( $index_pathname, 'w' );
     2036                if ( false === $file ) {
     2037                        wp_send_json_error( __( 'Unable to protect export folder from browsing' ) );
     2038                }
     2039                fwrite( $file, 'Silence is golden.' );
     2040                fclose( $file );
     2041        }
     2042
     2043        $stripped_email       = str_replace( '@', '-at-', $email_address );
     2044        $stripped_email       = sanitize_title( $stripped_email ); // slugify the email address
     2045        $obscura              = md5( rand() );
     2046        $file_basename        = 'wp-personal-data-file-' . $stripped_email . '-' . $obscura;
     2047        $html_report_filename = $file_basename . '.html';
     2048        $html_report_pathname = $exports_dir . $html_report_filename;
     2049        $file = fopen( $html_report_pathname, 'w' );
     2050        if ( false === $file ) {
     2051                wp_send_json_error( __( 'Unable to open export file (HTML report) for writing' ) );
     2052        }
     2053
     2054        $title = sprintf(
     2055                // translators: %s Users e-mail address.
     2056                __( 'Personal Data Export for %s' ),
     2057                $email_address
     2058        );
     2059
     2060        // Open HTML.
     2061        fwrite( $file, "<!DOCTYPE html>\n" );
     2062        fwrite( $file, "<html>\n" );
     2063
     2064        // Head.
     2065        fwrite( $file, "<head>\n" );
     2066        fwrite( $file, "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />\n" );
     2067        fwrite( $file, "<style type='text/css'>" );
     2068        fwrite( $file, "body { color: black; font-family: Arial, sans-serif; font-size: 11pt; margin: 15px auto; width: 860px; }" );
     2069        fwrite( $file, "table { background: #f0f0f0; border: 1px solid #ddd; margin-bottom: 20px; width: 100%; }" );
     2070        fwrite( $file, "th { padding: 5px; text-align: left; width: 20%; }" );
     2071        fwrite( $file, "td { padding: 5px; }" );
     2072        fwrite( $file, "tr:nth-child(odd) { background-color: #fafafa; }" );
     2073        fwrite( $file, "</style>" );
     2074        fwrite( $file, "<title>" );
     2075        fwrite( $file, esc_html( $title ) );
     2076        fwrite( $file, "</title>" );
     2077        fwrite( $file, "</head>\n" );
     2078
     2079        // Body.
     2080        fwrite( $file, "<body>\n" );
     2081
     2082        // Heading.
     2083        fwrite( $file, "<h1>" . esc_html__( 'Personal Data Export' ) . "</h1>" );
     2084
     2085        // And now, all the Groups.
     2086        $groups = get_post_meta( $request_id, '_export_data_grouped', true );
     2087
     2088        // First, build an "About" group on the fly for this report.
     2089        $about_group = array(
     2090                'group_label' => __( 'About' ),
     2091                'items'       => array(
     2092                        'about-1' => array(
     2093                                array(
     2094                                        'name'  => __( 'Report generated for' ),
     2095                                        'value' => $email_address,
     2096                                ),
     2097                                array(
     2098                                        'name'  => __( 'For site' ),
     2099                                        'value' => get_bloginfo( 'name' ),
     2100                                ),
     2101                                array(
     2102                                        'name'  => __( 'At URL' ),
     2103                                        'value' => get_bloginfo( 'url' ),
     2104                                ),
     2105                                array(
     2106                                        'name'  => __( 'On' ),
     2107                                        'value' => current_time( 'mysql' ),
     2108                                ),
     2109                        ),
     2110                ),
     2111        );
     2112
     2113        // Merge in the special about group.
     2114        $groups = array_merge( array( 'about' => $about_group ), $groups );
     2115
     2116        // Now, iterate over every group in $groups and have the formatter render it in HTML.
     2117        foreach ( (array) $groups as $group_id => $group_data ) {
     2118                fwrite( $file, wp_privacy_generate_personal_data_export_group_html( $group_data ) );
     2119        }
     2120
     2121        fwrite( $file, "</body>\n" );
     2122
     2123        // Close HTML.
     2124        fwrite( $file, "</html>\n" );
     2125        fclose( $file );
     2126
     2127        // Now, generate the ZIP.
     2128        $archive_filename = $file_basename . '.zip';
     2129        $archive_pathname = $exports_dir . $archive_filename;
     2130        $archive_url      = $exports_url . $archive_filename;
     2131
     2132        $zip = new ZipArchive;
     2133
     2134        if ( TRUE === $zip->open( $archive_pathname, ZipArchive::CREATE ) ) {
     2135                $zip->addFile( $html_report_pathname, 'index.html' );
     2136                $zip->close();
     2137        } else {
     2138                wp_send_json_error( __( 'Unable to open export file (archive) for writing' ) );
     2139        }
     2140
     2141        // And remove the HTML file.
     2142        unlink( $html_report_pathname );
     2143
     2144        // Save the export file in the request.
     2145        update_post_meta( $request_id, '_export_file_url', $archive_url );
     2146        update_post_meta( $request_id, '_export_file_path', $archive_pathname );
     2147}
     2148
     2149/**
     2150 * Send an email to the user with a link to the personal data export file
     2151 *
     2152 * @since 4.9.6
     2153 *
     2154 * @param int  $request_id  The request ID for this personal data export.
     2155 * @return true|WP_Error    True on success or `WP_Error` on failure.
     2156 */
     2157function wp_privacy_send_personal_data_export_email( $request_id ) {
     2158        // Get the request data.
     2159        $request = wp_get_user_request_data( $request_id );
     2160
     2161        if ( ! $request || 'export_personal_data' !== $request->action_name ) {
     2162                return new WP_Error( 'invalid', __( 'Invalid request ID when sending personal data export email.' ) );
     2163        }
     2164
     2165/* translators: Do not translate LINK, EMAIL, SITENAME, SITEURL: those are placeholders. */
     2166$email_text = __(
     2167'Howdy,
     2168
     2169Your request for an export of personal data has been completed. You may
     2170download your personal data by clicking on the link below. This link is
     2171good for the next 3 days.
     2172
     2173###LINK###
     2174
     2175This email has been sent to ###EMAIL###.
     2176
     2177Regards,
     2178All at ###SITENAME###
     2179###SITEURL###'
     2180);
     2181
     2182        /**
     2183         * Filters the text of the email sent with a personal data export file.
     2184         *
     2185         * The following strings have a special meaning and will get replaced dynamically:
     2186         * ###LINK###               URL of the personal data export file for the user.
     2187         * ###EMAIL###              The email we are sending to.
     2188         * ###SITENAME###           The name of the site.
     2189         * ###SITEURL###            The URL to the site.
     2190         *
     2191         * @since 4.9.6
     2192         *
     2193         * @param string $email_text     Text in the email.
     2194         * @param int    $request_id     The request ID for this personal data export.
     2195         */
     2196        $content = apply_filters( 'wp_privacy_personal_data_email_content', $email_text, $request_id );
     2197
     2198        $email_address = $request->email;
     2199        $export_file_url = get_post_meta( $request_id, '_export_file_url', true );
     2200        $site_name = is_multisite() ? get_site_option( 'site_name' ) : get_option( 'blogname' );
     2201        $site_url = network_home_url();
     2202
     2203        $content = str_replace( '###LINK###', esc_url_raw( $export_file_url ), $content );
     2204        $content = str_replace( '###EMAIL###', $email_address, $content );
     2205        $content = str_replace( '###SITENAME###', wp_specialchars_decode( $site_name, ENT_QUOTES ), $content );
     2206        $content = str_replace( '###SITEURL###', esc_url_raw( $site_url ), $content );
     2207
     2208        $mail_success = wp_mail(
     2209                $email_address,
     2210                sprintf(
     2211                        __( '[%s] Personal Data Export' ),
     2212                        wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES )
     2213                ),
     2214                $content
     2215        );
     2216
     2217        if ( ! $mail_success ) {
     2218                return new WP_Error( 'error', __( 'Unable to send personal data export email.' ) );
     2219        }
     2220
     2221        return true;
     2222}
     2223
     2224/**
     2225 * Intercept personal data exporter page ajax responses in order to assemble the personal data export file.
     2226 * @see wp_privacy_personal_data_export_page
     2227 * @since 4.9.6
     2228 *
     2229 * @param array  $response        The response from the personal data exporter for the given page.
     2230 * @param int    $exporter_index  The index of the personal data exporter. Begins at 1.
     2231 * @param string $email_address   The email address of the user whose personal data this is.
     2232 * @param int    $page            The page of personal data for this exporter. Begins at 1.
     2233 * @param int    $request_id      The request ID for this personal data export.
     2234 * @param bool   $send_as_email   Whether the final results of the export should be emailed to the user.
     2235 * @return array The filtered response.
     2236 */
     2237function wp_privacy_process_personal_data_export_page( $response, $exporter_index, $email_address, $page, $request_id, $send_as_email ) {
     2238        /* Do some simple checks on the shape of the response from the exporter.
     2239         * If the exporter response is malformed, don't attempt to consume it - let it
     2240         * pass through to generate a warning to the user by default ajax processing.
     2241         */
     2242        if ( ! is_array( $response ) ) {
     2243                return $response;
     2244        }
     2245
     2246        if ( ! array_key_exists( 'done', $response ) ) {
     2247                return $response;
     2248        }
     2249
     2250        if ( ! array_key_exists( 'data', $response ) ) {
     2251                return $response;
     2252        }
     2253
     2254        if ( ! is_array( $response['data'] ) ) {
     2255                return $response;
     2256        }
     2257
     2258        // Get the request data.
     2259        $request = wp_get_user_request_data( $request_id );
     2260
     2261        if ( ! $request || 'export_personal_data' !== $request->action_name ) {
     2262                wp_send_json_error( __( 'Invalid request ID when merging exporter data' ) );
     2263        }
     2264
     2265        $export_data = array();
     2266
     2267        // First exporter, first page? Reset the report data accumulation array.
     2268        if ( 1 === $exporter_index && 1 === $page ) {
     2269                update_post_meta( $request_id, '_export_data_raw', $export_data );
     2270        } else {
     2271                $export_data = get_post_meta( $request_id, '_export_data_raw', true );
     2272        }
     2273
     2274        // Now, merge the data from the exporter response into the data we have accumulated already.
     2275        $export_data = array_merge( $export_data, $response['data'] );
     2276        update_post_meta( $request_id, '_export_data_raw', $export_data );
     2277
     2278        // If we are not yet on the last page of the last exporter, return now.
     2279        $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() );
     2280        $is_last_exporter = $exporter_index === count( $exporters );
     2281        $exporter_done = $response['done'];
     2282        if ( ! $is_last_exporter || ! $exporter_done ) {
     2283                return $response;
     2284        }
     2285
     2286        // Last exporter, last page - let's prepare the export file.
     2287
     2288        // First we need to re-organize the raw data hierarchically in groups and items.
     2289        $groups = array();
     2290        foreach ( (array) $export_data as $export_datum ) {
     2291                $group_id    = $export_datum['group_id'];
     2292                $group_label = $export_datum['group_label'];
     2293                if ( ! array_key_exists( $group_id, $groups ) ) {
     2294                        $groups[ $group_id ] = array(
     2295                                'group_label' => $group_label,
     2296                                'items'       => array(),
     2297                        );
     2298                }
     2299
     2300                $item_id = $export_datum['item_id'];
     2301                if ( ! array_key_exists( $item_id, $groups[ $group_id ]['items'] ) ) {
     2302                        $groups[ $group_id ]['items'][ $item_id ] = array();
     2303                }
     2304
     2305                $old_item_data = $groups[ $group_id ]['items'][ $item_id ];
     2306                $merged_item_data = array_merge( $export_datum['data'], $old_item_data );
     2307                $groups[ $group_id ]['items'][ $item_id ] = $merged_item_data;
     2308        }
     2309
     2310        // Then save the grouped data into the request.
     2311        delete_post_meta( $request_id, '_export_data_raw' );
     2312        update_post_meta( $request_id, '_export_data_grouped', $groups );
     2313
     2314        // And now, generate the export file, cleaning up any previous file
     2315        $export_path = get_post_meta( $request_id, '_export_file_path', true );
     2316        if ( ! empty( $export_path ) ) {
     2317                delete_post_meta( $request_id, '_export_file_path' );
     2318                @unlink( $export_path );
     2319        }
     2320        delete_post_meta( $request_id, '_export_file_url' );
     2321
     2322        // Generate the export file from the collected, grouped personal data.
     2323        do_action( 'wp_privacy_personal_data_export_file', $request_id );
     2324
     2325        // Clear the grouped data now that it is no longer needed.
     2326        delete_post_meta( $request_id, '_export_data_grouped' );
     2327
     2328        // If the destination is email, send it now.
     2329        if ( $send_as_email ) {
     2330                $mail_success = wp_privacy_send_personal_data_export_email( $request_id );
     2331                if ( is_wp_error( $mail_success ) ) {
     2332                        wp_send_json_error( $mail_success->get_error_message() );
     2333                }
     2334        } else {
     2335                // Modify the response to include the URL of the export file so the browser can fetch it.
     2336                $export_file_url = get_post_meta( $request_id, '_export_file_url', true );
     2337                if ( ! empty( $export_file_url ) ) {
     2338                        $response['url'] = $export_file_url;
     2339                }
     2340        }
     2341
     2342        // Update the request to completed state.
     2343        _wp_privacy_completed_request( $request_id );
     2344
     2345        return $response;
     2346}
     2347
     2348/**
     2349 * Cleans up export files older than three days old.
     2350 *
     2351 * @since 4.9.6
     2352 */
     2353function wp_privacy_delete_old_export_files() {
     2354        $upload_dir   = wp_upload_dir();
     2355        $exports_dir  = trailingslashit( $upload_dir['basedir'] . '/exports' );
     2356        $export_files = list_files( $exports_dir );
     2357
     2358        foreach( (array) $export_files as $export_file ) {
     2359                $file_age_in_seconds = time() - filemtime( $export_file );
     2360
     2361                if ( 3 * DAY_IN_SECONDS < $file_age_in_seconds ) {
     2362                        @unlink( $export_file );
     2363                }
     2364        }
     2365}
  • trunk/src/wp-admin/includes/user.php

    r43011 r43012  
    661661                                'privacy_action_email_retry',
    662662                                __( 'Confirmation request re-resent successfully.' ),
    663                                 'updated'
    664                         );
    665                 }
    666 
    667         } elseif ( isset( $_POST['export_personal_data_email_send'] ) ) { // WPCS: input var ok.
    668                 check_admin_referer( 'bulk-privacy_requests' );
    669 
    670                 $request_id = absint( current( array_keys( (array) wp_unslash( $_POST['export_personal_data_email_send'] ) ) ) ); // WPCS: input var ok, sanitization ok.
    671                 $result     = false;
    672 
    673                 /**
    674                  * TODO: Email the data to the user here.
    675                  */
    676 
    677                 if ( is_wp_error( $result ) ) {
    678                         add_settings_error(
    679                                 'export_personal_data_email_send',
    680                                 'export_personal_data_email_send',
    681                                 $result->get_error_message(),
    682                                 'error'
    683                         );
    684                 } else {
    685                         _wp_privacy_completed_request( $request_id );
    686                         add_settings_error(
    687                                 'export_personal_data_email_send',
    688                                 'export_personal_data_email_send',
    689                                 __( 'Personal data was sent to the user successfully.' ),
    690663                                'updated'
    691664                        );
     
    820793        _wp_personal_data_cleanup_requests();
    821794
     795        // "Borrow" xfn.js for now so we don't have to create new files.
     796        wp_enqueue_script( 'xfn' );
     797
    822798        $requests_table = new WP_Privacy_Data_Export_Requests_Table( array(
    823799                'plural'   => 'privacy_requests',
     
    13621338                $nonce           = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id );
    13631339
    1364                 $download_data_markup = '<div class="download_personal_data" ' .
     1340                $download_data_markup = '<div class="export_personal_data" ' .
    13651341                        'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' .
    13661342                        'data-request-id="' . esc_attr( $request_id ) . '" ' .
     
    13681344                        '">';
    13691345
    1370                 $download_data_markup .= '<span class="download_personal_data_idle"><a href="#" >' . __( 'Download Personal Data' ) . '</a></span>' .
    1371                         '<span style="display:none" class="download_personal_data_processing" >' . __( 'Downloading Data...' ) . '</span>' .
    1372                         '<span style="display:none" class="download_personal_data_failed">' . __( 'Download Failed!' ) . ' <a href="#" >' . __( 'Retry' ) . '</a></span>';
     1346                $download_data_markup .= '<span class="export_personal_data_idle"><a href="#" >' . __( 'Download Personal Data' ) . '</a></span>' .
     1347                        '<span style="display:none" class="export_personal_data_processing" >' . __( 'Downloading Data...' ) . '</span>' .
     1348                        '<span style="display:none" class="export_personal_data_success"><a href="#" >' . __( 'Download Personal Data Again' ) . '</a></span>' .
     1349                        '<span style="display:none" class="export_personal_data_failed">' . __( 'Download Failed!' ) . ' <a href="#" >' . __( 'Retry' ) . '</a></span>';
     1350
     1351                $download_data_markup .= '</div>';
    13731352
    13741353                $row_actions = array(
     
    13941373                                break;
    13951374                        case 'request-confirmed':
    1396                                 // TODO Complete in follow on patch.
     1375                                $exporters       = apply_filters( 'wp_privacy_personal_data_exporters', array() );
     1376                                $exporters_count = count( $exporters );
     1377                                $request_id      = $item->ID;
     1378                                $nonce           = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id );
     1379
     1380                                echo '<div class="export_personal_data" ' .
     1381                                        'data-send-as-email="1" ' .
     1382                                        'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' .
     1383                                        'data-request-id="' . esc_attr( $request_id ) . '" ' .
     1384                                        'data-nonce="' . esc_attr( $nonce ) .
     1385                                        '">';
     1386
     1387                                ?>
     1388                                <span class="export_personal_data_idle"><a class="button" href="#" ><?php _e( 'Email Data' ); ?></a></span>
     1389                                <span style="display:none" class="export_personal_data_processing button updating-message" ><?php _e( 'Sending Email...' ); ?></span>
     1390                                <span style="display:none" class="export_personal_data_success success-message" ><?php _e( 'Email Sent!' ); ?></span>
     1391                                <span style="display:none" class="export_personal_data_failed"><?php _e( 'Email Failed!' ); ?> <a class="button" href="#" ><?php _e( 'Retry' ); ?></a></span>
     1392                                <?php
     1393
     1394                                echo '</div>';
    13971395                                break;
    13981396                        case 'request-failed':
     
    14621460                                '<span style="display:none" class="remove_personal_data_failed">' . __( 'Force Remove Failed!' ) . ' <a href="#" >' . __( 'Retry' ) . '</a></span>';
    14631461
     1462                        $remove_data_markup .= '</div>';
     1463
    14641464                        $row_actions = array(
    14651465                                'remove_data' => $remove_data_markup,
     
    15021502                                <span style="display:none" class="remove_personal_data_failed"><?php _e( 'Removing Data Failed!' ); ?> <a class="button" href="#" ><?php _e( 'Retry' ); ?></a></span>
    15031503                                <?php
     1504
     1505                                echo '</div>';
    15041506
    15051507                                break;
  • trunk/src/wp-admin/js/xfn.js

    r42994 r43012  
    2323
    2424// Privacy request action handling
    25 
    2625jQuery( document ).ready( function( $ ) {
    2726        var strings = window.privacyToolsL10n || {};
     
    4039        function appendResultsAfterRow( $requestRow, classes, summaryMessage, additionalMessages ) {
    4140                clearResultsAfterRow( $requestRow );
     41
     42                var itemList = '';
    4243                if ( additionalMessages.length ) {
    43                         // TODO - render additionalMessages after the summaryMessage
     44                        $.each( additionalMessages, function( index, value ) {
     45                                itemList = itemList + '<li>' + value + '</li>';
     46                        } );
     47                        itemList = '<ul>' + itemList + '</ul>';
    4448                }
    4549
    4650                $requestRow.after( function() {
    47                         return '<tr class="request-results"><td colspan="5"><div class="notice inline notice-alt ' + classes + '"><p>' +
    48                                 summaryMessage +
    49                                 '</p></div></td></tr>';
     51                        return '<tr class="request-results"><td colspan="5">' +
     52                                '<div class="notice inline notice-alt ' + classes + '">' +
     53                                '<p>' + summaryMessage + '</p>' +
     54                                itemList +
     55                                '</div>' +
     56                                '</td>' +
     57                                '</tr>';
    5058                } );
    5159        }
     60
     61        $( '.export_personal_data a' ).click( function( event ) {
     62                event.preventDefault();
     63                event.stopPropagation();
     64
     65                var $this          = $( this );
     66                var $action        = $this.parents( '.export_personal_data' );
     67                var $requestRow    = $this.parents( 'tr' );
     68                var requestID      = $action.data( 'request-id' );
     69                var nonce          = $action.data( 'nonce' );
     70                var exportersCount = $action.data( 'exporters-count' );
     71                var sendAsEmail    = $action.data( 'send-as-email' ) ? true : false;
     72
     73                $action.blur();
     74                clearResultsAfterRow( $requestRow );
     75
     76                function on_export_done_success( zipUrl ) {
     77                        set_action_state( $action, 'export_personal_data_success' );
     78                        if ( 'undefined' !== typeof zipUrl ) {
     79                                window.location = zipUrl;
     80                        } else if ( ! sendAsEmail ) {
     81                                on_export_failure( strings.noExportFile );
     82                        }
     83                }
     84
     85                function on_export_failure( errorMessage ) {
     86                        set_action_state( $action, 'export_personal_data_failed' );
     87                        if ( errorMessage ) {
     88                                appendResultsAfterRow( $requestRow, 'notice-error', strings.exportError, [ errorMessage ] );
     89                        }
     90                }
     91
     92                function do_next_export( exporterIndex, pageIndex ) {
     93                        $.ajax(
     94                                {
     95                                        url: window.ajaxurl,
     96                                        data: {
     97                                                action: 'wp-privacy-export-personal-data',
     98                                                exporter: exporterIndex,
     99                                                id: requestID,
     100                                                page: pageIndex,
     101                                                security: nonce,
     102                                                sendAsEmail: sendAsEmail
     103                                        },
     104                                        method: 'post'
     105                                }
     106                        ).done( function( response ) {
     107                                if ( ! response.success ) {
     108                                        // e.g. invalid request ID
     109                                        on_export_failure( response.data );
     110                                        return;
     111                                }
     112                                var responseData = response.data;
     113                                if ( ! responseData.done ) {
     114                                        setTimeout( do_next_export( exporterIndex, pageIndex + 1 ) );
     115                                } else {
     116                                        if ( exporterIndex < exportersCount ) {
     117                                                setTimeout( do_next_export( exporterIndex + 1, 1 ) );
     118                                        } else {
     119                                                on_export_done_success( responseData.url );
     120                                        }
     121                                }
     122                        } ).fail( function( jqxhr, textStatus, error ) {
     123                                // e.g. Nonce failure
     124                                on_export_failure( error );
     125                        } );
     126                }
     127
     128                // And now, let's begin
     129                set_action_state( $action, 'export_personal_data_processing' );
     130                do_next_export( 1, 1 );
     131        } );
    52132
    53133        $( '.remove_personal_data a' ).click( function( event ) {
     
    93173                function on_erasure_failure() {
    94174                        set_action_state( $action, 'remove_personal_data_failed' );
    95                         appendResultsAfterRow( $requestRow, 'notice-error', strings.anErrorOccurred, [] );
     175                        appendResultsAfterRow( $requestRow, 'notice-error', strings.removalError, [] );
    96176                }
    97177
  • trunk/src/wp-includes/comment.php

    r42994 r43012  
    33533353                                case 'comment_link':
    33543354                                        $value = get_comment_link( $comment->comment_ID );
     3355                                        $value = '<a href="' . $value . '" target="_blank" rel="noreferrer noopener">' . $value . '</a>';
    33553356                                        break;
    33563357                        }
  • trunk/src/wp-includes/script-loader.php

    r42986 r43012  
    716716                                'noneRemoved'     => __( 'Personal data was found for this user but was not removed.' ),
    717717                                'someNotRemoved'  => __( 'Personal data was found for this user but some of the personal data found was not removed.' ),
    718                                 'anErrorOccurred' => __( 'An error occurred while attempting to find and remove personal data.' ),
     718                                'removalError'    => __( 'An error occurred while attempting to find and remove personal data.' ),
     719                                'noExportFile'    => __( 'No personal data export file was generated.' ),
     720                                'exportError'     => __( 'An error occurred while attempting to export personal data.' ),
    719721                        )
    720722                );
  • trunk/src/wp-includes/user.php

    r43011 r43012  
    31463146 *
    31473147 * @param int $request_id Request ID to get data about.
    3148  * @return array|false
     3148 * @return WP_User_Request|false
    31493149 */
    31503150function wp_get_user_request_data( $request_id ) {
Note: See TracChangeset for help on using the changeset viewer.