WordPress.org

Make WordPress Core

Ticket #43546: 43546.7.diff

File 43546.7.diff, 28.4 KB (added by allendav, 2 years ago)

Adds direct-to-email export and marking of export requests as complete. Fixes divs lacking closing tags.

  • src/wp-admin/includes/admin-filters.php

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

     
    43284328}
    43294329
    43304330function wp_ajax_wp_privacy_export_personal_data() {
    4331         check_ajax_referer( 'wp-privacy-export-personal-data', 'security' );
     4331        $request_id  = (int) $_POST['id'];
    43324332
     4333        if ( empty( $request_id ) ) {
     4334                wp_send_json_error( __( 'Error: Invalid request ID.' ) );
     4335        }
     4336
    43334337        if ( ! current_user_can( 'manage_options' ) ) {
    43344338                wp_send_json_error( __( 'Error: Invalid request.' ) );
    43354339        }
    43364340
    4337         $email_address  = sanitize_text_field( $_POST['email'] );
     4341        check_ajax_referer( 'wp-privacy-export-personal-data-' . $request_id, 'security' );
     4342
     4343        // Find the request CPT
     4344        $request = get_post( $request_id );
     4345        if ( 'user_export_request' !== $request->post_type ) {
     4346                wp_send_json_error( 'internal error: invalid request id' );
     4347        }
     4348
     4349        $email_address = get_post_meta( $request_id, '_user_email', true );
     4350
     4351        if ( ! is_email( $email_address ) ) {
     4352                wp_send_json_error( 'A valid email address must be given.' );
     4353        }
     4354
    43384355        $exporter_index = (int) $_POST['exporter'];
    43394356        $page           = (int) $_POST['page'];
     4357        $send_as_email  = isset( $_POST['sendAsEmail'] ) ? ( "true" === $_POST['sendAsEmail'] ) : false;
    43404358
    43414359        /**
    43424360         * Filters the array of exporter callbacks.
     
    43754393                        wp_send_json_error( 'Page index cannot be less than one.' );
    43764394                }
    43774395
    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 
    43854396                $exporter = $exporters[ $index ];
    43864397                if ( ! is_array( $exporter ) ) {
    43874398                        wp_send_json_error( "Expected an array describing the exporter at index {$exporter_index}." );
     
    44354446         * @param int    $exporter_index  The index of the exporter that provided this data.
    44364447         * @param string $email_address   The email address associated with this personal data.
    44374448         * @param int    $page            The zero-based page for this response.
     4449         * @param int    $request_id      The privacy request post ID associated with this request.
     4450         * @param bool   $send_as_email   Whether the final results of the export should be emailed to the user.
    44384451         */
    4439         $response = apply_filters( 'wp_privacy_personal_data_export_page', $response, $exporter_index, $email_address, $page );
     4452        $response = apply_filters( 'wp_privacy_personal_data_export_page', $response, $exporter_index, $email_address, $page, $request_id, $send_as_email );
    44404453        if ( is_wp_error( $response ) ) {
    44414454                wp_send_json_error( $response );
    44424455        }
  • src/wp-admin/includes/file.php

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

     
    731731                        );
    732732                }
    733733
    734         } elseif ( isset( $_POST['export_personal_data_email_send'] ) ) { // WPCS: input var ok.
    735                 check_admin_referer( 'bulk-privacy_requests' );
    736 
    737                 $request_id = absint( current( array_keys( (array) wp_unslash( $_POST['export_personal_data_email_send'] ) ) ) ); // WPCS: input var ok, sanitization ok.
    738                 $result     = false;
    739 
    740                 /**
    741                  * TODO: Email the data to the user here.
    742                  */
    743 
    744                 if ( is_wp_error( $result ) ) {
    745                         add_settings_error(
    746                                 'export_personal_data_email_send',
    747                                 'export_personal_data_email_send',
    748                                 $result->get_error_message(),
    749                                 'error'
    750                         );
    751                 } else {
    752                         _wp_privacy_completed_request( $request_id );
    753                         add_settings_error(
    754                                 'export_personal_data_email_send',
    755                                 'export_personal_data_email_send',
    756                                 __( 'Personal data was sent to the user successfully.' ),
    757                                 'updated'
    758                         );
    759                 }
    760 
    761734        } elseif ( isset( $_POST['action'] ) ) {
    762735                $action = isset( $_POST['action'] ) ? sanitize_key( wp_unslash( $_POST['action'] ) ) : ''; // WPCS: input var ok, CSRF ok.
    763736
     
    847820
    848821        _wp_personal_data_handle_actions();
    849822
     823        // "Borrow" xfn.js for now so we don't have to create new files.
     824        wp_enqueue_script( 'xfn' );
     825
    850826        $requests_table = new WP_Privacy_Data_Export_Requests_Table( array(
    851827                'plural'   => 'privacy_requests',
    852828                'singular' => 'privacy_request',
     
    13681344                $request_id      = $item['request_id'];
    13691345                $nonce           = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id );
    13701346
    1371                 $download_data_markup = '<div class="download_personal_data" ' .
     1347                $download_data_markup = '<div class="export_personal_data" ' .
    13721348                        'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' .
    13731349                        'data-request-id="' . esc_attr( $request_id ) . '" ' .
    13741350                        'data-nonce="' . esc_attr( $nonce ) .
    13751351                        '">';
    13761352
    1377                 $download_data_markup .= '<span class="download_personal_data_idle"><a href="#" >' . __( 'Download Personal Data' ) . '</a></span>' .
    1378                         '<span style="display:none" class="download_personal_data_processing" >' . __( 'Downloading Data...' ) . '</span>' .
    1379                         '<span style="display:none" class="download_personal_data_failed">' . __( 'Download Failed!' ) . ' <a href="#" >' . __( 'Retry' ) . '</a></span>';
     1353                $download_data_markup .= '<span class="export_personal_data_idle"><a href="#" >' . __( 'Download Personal Data' ) . '</a></span>' .
     1354                        '<span style="display:none" class="export_personal_data_processing" >' . __( 'Downloading Data...' ) . '</span>' .
     1355                        '<span style="display:none" class="export_personal_data_success"><a href="#" >' . __( 'Download Personal Data Again' ) . '</a></span>' .
     1356                        '<span style="display:none" class="export_personal_data_failed">' . __( 'Download Failed!' ) . ' <a href="#" >' . __( 'Retry' ) . '</a></span>';
    13801357
     1358                $download_data_markup .= '</div>';
     1359
    13811360                $row_actions = array(
    13821361                        'download_data' => $download_data_markup,
    13831362                );
     
    14001379                                esc_html_e( 'Waiting for confirmation' );
    14011380                                break;
    14021381                        case 'request-confirmed':
    1403                                 // TODO Complete in follow on patch.
     1382                                $exporters       = apply_filters( 'wp_privacy_personal_data_exporters', array() );
     1383                                $exporters_count = count( $exporters );
     1384                                $request_id      = $item['request_id'];
     1385                                $nonce           = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id );
     1386
     1387                                echo '<div class="export_personal_data" ' .
     1388                                        'data-send-as-email="1" ' .
     1389                                        'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' .
     1390                                        'data-request-id="' . esc_attr( $request_id ) . '" ' .
     1391                                        'data-nonce="' . esc_attr( $nonce ) .
     1392                                        '">';
     1393
     1394                                ?>
     1395                                <span class="export_personal_data_idle"><a class="button" href="#" ><?php _e( 'Email Data' ); ?></a></span>
     1396                                <span style="display:none" class="export_personal_data_processing button updating-message" ><?php _e( 'Sending Email...' ); ?></span>
     1397                                <span style="display:none" class="export_personal_data_success success-message" ><?php _e( 'Email Sent!' ); ?></span>
     1398                                <span style="display:none" class="export_personal_data_failed"><?php _e( 'Email Failed!' ); ?> <a class="button" href="#" ><?php _e( 'Retry' ); ?></a></span>
     1399                                <?php
     1400
     1401                                echo '</div>';
    14041402                                break;
    14051403                        case 'request-failed':
    14061404                                submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item['request_id'] . ']', false );
     
    14681466                                '<span style="display:none" class="remove_personal_data_processing" >' . __( 'Removing Data...' ) . '</span>' .
    14691467                                '<span style="display:none" class="remove_personal_data_failed">' . __( 'Force Remove Failed!' ) . ' <a href="#" >' . __( 'Retry' ) . '</a></span>';
    14701468
     1469                        $remove_data_markup .= '</div>';
     1470
    14711471                        $row_actions = array(
    14721472                                'remove_data' => $remove_data_markup,
    14731473                        );
     
    15091509                                <span style="display:none" class="remove_personal_data_failed"><?php _e( 'Removing Data Failed!' ); ?> <a class="button" href="#" ><?php _e( 'Retry' ); ?></a></span>
    15101510                                <?php
    15111511
     1512                                echo '</div>';
     1513
    15121514                                break;
    15131515                        case 'request-failed':
    15141516                                submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item['request_id'] . ']', false );
  • src/wp-admin/js/xfn.js

     
    3939
    4040        function appendResultsAfterRow( $requestRow, classes, summaryMessage, additionalMessages ) {
    4141                clearResultsAfterRow( $requestRow );
     42
     43                var itemList = '';
    4244                if ( additionalMessages.length ) {
    43                         // TODO - render additionalMessages after the summaryMessage
     45                        $.each( additionalMessages, function( index, value ) {
     46                                itemList = itemList + '<li>' + value + '</li>';
     47                        } );
     48                        itemList = '<ul>' + itemList + '</ul>';
    4449                }
    4550
    4651                $requestRow.after( function() {
    47                         return '<tr class="request-results"><td colspan="5"><div class="notice inline notice-alt ' + classes + '"><p>' +
     52                        return '<tr class="request-results"><td colspan="5">' +
     53                                '<div class="notice inline notice-alt ' + classes + '">' +
     54                                '<p>' +
    4855                                summaryMessage +
    49                                 '</p></div></td></tr>';
     56                                '</p>' +
     57                                itemList +
     58                                '</div>' +
     59                                '</td>' +
     60                                '</tr>';
    5061                } );
    5162        }
    5263
     64        $( '.export_personal_data a' ).click( function( event ) {
     65                event.preventDefault();
     66                event.stopPropagation();
     67
     68                var $this          = $( this );
     69                var $action        = $this.parents( '.export_personal_data' );
     70                var $requestRow    = $this.parents( 'tr' );
     71                var requestID      = $action.data( 'request-id' );
     72                var nonce          = $action.data( 'nonce' );
     73                var exportersCount = $action.data( 'exporters-count' );
     74                var sendAsEmail    = $action.data( 'send-as-email' ) ? true : false;
     75
     76                $action.blur();
     77                clearResultsAfterRow( $requestRow );
     78
     79                function on_export_done_success( zipUrl ) {
     80                        set_action_state( $action, 'export_personal_data_success' );
     81                        if ( 'undefined' !== typeof zipUrl ) {
     82                                window.location = zipUrl;
     83                        } else if ( ! sendAsEmail ) {
     84                                on_export_failure( strings.noExportFile );
     85                        }
     86                }
     87
     88                function on_export_failure( errorMessage ) {
     89                        set_action_state( $action, 'export_personal_data_failed' );
     90                        if ( errorMessage ) {
     91                                appendResultsAfterRow( $requestRow, 'notice-error', strings.exportError, [ errorMessage ] );
     92                        }
     93                }
     94
     95                function do_next_export( exporterIndex, pageIndex ) {
     96                        $.ajax(
     97                                {
     98                                        url: ajaxurl,
     99                                        data: {
     100                                                action: 'wp-privacy-export-personal-data',
     101                                                exporter: exporterIndex,
     102                                                id: requestID,
     103                                                page: pageIndex,
     104                                                security: nonce,
     105                                                sendAsEmail: sendAsEmail,
     106                                        },
     107                                        method: 'post'
     108                                }
     109                        ).done( function( response ) {
     110                                if ( ! response.success ) {
     111                                        // e.g. invalid request ID
     112                                        on_export_failure( response.data );
     113                                        return;
     114                                }
     115                                var responseData = response.data;
     116                                if ( ! responseData.done ) {
     117                                        setTimeout( do_next_export( exporterIndex, pageIndex + 1 ) );
     118                                } else {
     119                                        if ( exporterIndex < exportersCount ) {
     120                                                setTimeout( do_next_export( exporterIndex + 1, 1 ) );
     121                                        } else {
     122                                                on_export_done_success( responseData.url );
     123                                        }
     124                                }
     125                        } ).fail( function( jqxhr, textStatus, error ) {
     126                                // e.g. Nonce failure
     127                                on_export_failure( error );
     128                        } );
     129                }
     130
     131                // And now, let's begin
     132                set_action_state( $action, 'export_personal_data_processing' );
     133                do_next_export( 1, 1 );
     134        } )
     135
    53136        $( '.remove_personal_data a' ).click( function( event ) {
    54137                event.preventDefault();
    55138                event.stopPropagation();
     
    92175
    93176                function on_erasure_failure() {
    94177                        set_action_state( $action, 'remove_personal_data_failed' );
    95                         appendResultsAfterRow( $requestRow, 'notice-error', strings.anErrorOccurred, [] );
     178                        appendResultsAfterRow( $requestRow, 'notice-error', strings.removalError, [] );
    96179                }
    97180
    98181                function do_next_erasure( eraserIndex, pageIndex ) {
  • src/wp-includes/comment.php

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

     
    715715                                'foundAndRemoved' => __( 'All of the personal data found for this user was removed.' ),
    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                );
    721723