Make WordPress Core

Changeset 43092


Ignore:
Timestamp:
05/02/2018 02:15:05 AM (8 years ago)
Author:
SergeyBiryukov
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.
Merges [43012] and [43089] to the 4.9 branch.
See #43546.

Location:
branches/4.9
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • branches/4.9

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

    r43083 r43092  
    132132add_action( 'upgrader_process_complete', 'wp_update_plugins', 10, 0 );
    133133add_action( 'upgrader_process_complete', 'wp_update_themes', 10, 0 );
     134
     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 );
  • branches/4.9/src/wp-admin/includes/ajax-actions.php

    r43084 r43092  
    40194019}
    40204020
     4021/**
     4022 * Ajax handler for exporting a user's personal data.
     4023 *
     4024 * @since 4.9.6
     4025 */
    40214026function wp_ajax_wp_privacy_export_personal_data() {
    4022         check_ajax_referer( 'wp-privacy-export-personal-data', 'security' );
     4027        $request_id  = (int) $_POST['id'];
     4028
     4029        if ( empty( $request_id ) ) {
     4030                wp_send_json_error( __( 'Error: Invalid request ID.' ) );
     4031        }
    40234032
    40244033        if ( ! current_user_can( 'manage_options' ) ) {
     
    40264035        }
    40274036
    4028         $email_address  = sanitize_text_field( $_POST['email'] );
     4037        check_ajax_referer( 'wp-privacy-export-personal-data-' . $request_id, 'security' );
     4038
     4039        // Get the request data.
     4040        $request = wp_get_user_request_data( $request_id );
     4041
     4042        if ( ! $request || 'export_personal_data' !== $request->action_name ) {
     4043                wp_send_json_error( __( 'Error: Invalid request type.' ) );
     4044        }
     4045
     4046        $email_address = $request->email;
     4047        if ( ! is_email( $email_address ) ) {
     4048                wp_send_json_error( __( 'Error: A valid email address must be given.' ) );
     4049        }
     4050
    40294051        $exporter_index = (int) $_POST['exporter'];
    40304052        $page           = (int) $_POST['page'];
     4053        $send_as_email  = isset( $_POST['sendAsEmail'] ) ? ( "true" === $_POST['sendAsEmail'] ) : false;
    40314054
    40324055        /**
     
    40404063         *         callback               string  Callable exporter that accepts an email address and
    40414064         *                                        a page and returns an array of name => value
    4042          *                                        pairs of personal data
    4043          *         exporter_friendly_name string  Translated user facing friendly name for the exporter
     4065         *                                        pairs of personal data.
     4066         *         exporter_friendly_name string  Translated user facing friendly name for the exporter.
    40444067         *     ]
    40454068         * }
     
    40674090                }
    40684091
    4069                 // Surprisingly, email addresses can contain mutli-byte characters now
    4070                 $email_address = trim( mb_strtolower( $email_address ) );
    4071 
    4072                 if ( ! is_email( $email_address ) ) {
    4073                         wp_send_json_error( 'A valid email address must be given.' );
    4074                 }
    4075 
    40764092                $exporter = $exporters[ $index ];
     4093
    40774094                if ( ! is_array( $exporter ) ) {
    40784095                        wp_send_json_error( "Expected an array describing the exporter at index {$exporter_index}." );
    40794096                }
    4080                 if ( ! array_key_exists( 'callback', $exporter ) ) {
    4081                         wp_send_json_error( "Exporter array at index {$exporter_index} does not include a callback." );
    4082                 }
    4083                 if ( ! is_callable( $exporter['callback'] ) ) {
    4084                         wp_send_json_error( "Exporter callback at index {$exporter_index} is not a valid callback." );
    4085                 }
    40864097                if ( ! array_key_exists( 'exporter_friendly_name', $exporter ) ) {
    40874098                        wp_send_json_error( "Exporter array at index {$exporter_index} does not include a friendly name." );
     4099                }
     4100                if ( ! array_key_exists( 'callback', $exporter ) ) {
     4101                        wp_send_json_error( "Exporter does not include a callback: {$exporter['exporter_friendly_name']}." );
     4102                }
     4103                if ( ! is_callable( $exporter['callback'] ) ) {
     4104                        wp_send_json_error( "Exporter callback is not a valid callback: {$exporter['exporter_friendly_name']}." );
    40884105                }
    40894106
     
    41094126                }
    41104127        } else {
    4111                 // No exporters, so we're done
     4128                // No exporters, so we're done.
    41124129                $response = array(
    41134130                        'data' => array(),
     
    41274144         * @param string $email_address   The email address associated with this personal data.
    41284145         * @param int    $page            The zero-based page for this response.
     4146         * @param int    $request_id      The privacy request post ID associated with this request.
     4147         * @param bool   $send_as_email   Whether the final results of the export should be emailed to the user.
    41294148         */
    4130         $response = apply_filters( 'wp_privacy_personal_data_export_page', $response, $exporter_index, $email_address, $page );
     4149        $response = apply_filters( 'wp_privacy_personal_data_export_page', $response, $exporter_index, $email_address, $page, $request_id, $send_as_email );
     4150
    41314151        if ( is_wp_error( $response ) ) {
    41324152                wp_send_json_error( $response );
     
    41544174        check_ajax_referer( 'wp-privacy-erase-personal-data-' . $request_id, 'security' );
    41554175
    4156         // Find the request CPT
     4176        // Get the request data.
    41574177        $request = wp_get_user_request_data( $request_id );
    41584178
  • branches/4.9/src/wp-admin/includes/file.php

    r42811 r43092  
    17981798        <?php
    17991799}
     1800
     1801/**
     1802 * Generate a single group for the personal data export report.
     1803 *
     1804 * @since 4.9.6
     1805 *
     1806 * @param array  $group_data {
     1807 *     The group data to render.
     1808 *
     1809 *     @type string $group_label  The user-facing heading for the group, e.g. 'Comments'.
     1810 *     @type array  $items        {
     1811 *         An array of group items.
     1812 *
     1813 *         @type array  $group_item_data  {
     1814 *             An array of name-value pairs for the item.
     1815 *
     1816 *             @type string $name   The user-facing name of an item name-value pair, e.g. 'IP Address'.
     1817 *             @type string $value  The user-facing value of an item data pair, e.g. '50.60.70.0'.
     1818 *         }
     1819 *     }
     1820 * }
     1821 * @return string The HTML for this group and its items.
     1822 */
     1823function wp_privacy_generate_personal_data_export_group_html( $group_data ) {
     1824        $allowed_tags      = array(
     1825                'a' => array(
     1826                        'href'   => array(),
     1827                        'target' => array()
     1828                ),
     1829                'br' => array()
     1830        );
     1831        $allowed_protocols = array( 'http', 'https' );
     1832        $group_html        = '';
     1833
     1834        $group_html .= '<h2>' . esc_html( $group_data['group_label'] ) . '</h2>';
     1835        $group_html .= '<div>';
     1836
     1837        foreach ( (array) $group_data['items'] as $group_item_id => $group_item_data ) {
     1838                $group_html .= '<table>';
     1839                $group_html .= '<tbody>';
     1840
     1841                foreach ( (array) $group_item_data as $group_item_datum ) {
     1842                        $group_html .= '<tr>';
     1843                        $group_html .= '<th>' . esc_html( $group_item_datum['name'] ) . '</th>';
     1844                        $group_html .= '<td>' . wp_kses( $group_item_datum['value'], $allowed_tags, $allowed_protocols ) . '</td>';
     1845                        $group_html .= '</tr>';
     1846                }
     1847
     1848                $group_html .= '</tbody>';
     1849                $group_html .= '</table>';
     1850        }
     1851
     1852        $group_html .= '</div>';
     1853
     1854        return $group_html;
     1855}
     1856
     1857/**
     1858 * Generate the personal data export file.
     1859 *
     1860 * @since 4.9.6
     1861 *
     1862 * @param int  $request_id  The export request ID.
     1863 */
     1864function wp_privacy_generate_personal_data_export_file( $request_id ) {
     1865        // Maybe make this a cron job instead.
     1866        wp_privacy_delete_old_export_files();
     1867
     1868        if ( ! class_exists( 'ZipArchive' ) ) {
     1869                wp_send_json_error( __( 'Unable to generate export file. ZipArchive not available.' ) );
     1870        }
     1871
     1872        // Get the request data.
     1873        $request = wp_get_user_request_data( $request_id );
     1874
     1875        if ( ! $request || 'export_personal_data' !== $request->action_name ) {
     1876                wp_send_json_error( __( 'Invalid request ID when generating export file' ) );
     1877        }
     1878
     1879        $email_address = $request->email;
     1880
     1881        if ( ! is_email( $email_address ) ) {
     1882                wp_send_json_error( __( 'Invalid email address when generating export file' ) );
     1883        }
     1884
     1885        // Create the exports folder if needed.
     1886        $upload_dir  = wp_upload_dir();
     1887        $exports_dir = trailingslashit( $upload_dir['basedir'] . '/exports' );
     1888        $exports_url = trailingslashit( $upload_dir['baseurl'] . '/exports' );
     1889
     1890        $result = wp_mkdir_p( $exports_dir );
     1891        if ( is_wp_error( $result ) ) {
     1892                wp_send_json_error( $result->get_error_message() );
     1893        }
     1894
     1895        // Protect export folder from browsing.
     1896        $index_pathname = $exports_dir . 'index.html';
     1897        if ( ! file_exists( $index_pathname ) ) {
     1898                $file = fopen( $index_pathname, 'w' );
     1899                if ( false === $file ) {
     1900                        wp_send_json_error( __( 'Unable to protect export folder from browsing' ) );
     1901                }
     1902                fwrite( $file, 'Silence is golden.' );
     1903                fclose( $file );
     1904        }
     1905
     1906        $stripped_email       = str_replace( '@', '-at-', $email_address );
     1907        $stripped_email       = sanitize_title( $stripped_email ); // slugify the email address
     1908        $obscura              = md5( rand() );
     1909        $file_basename        = 'wp-personal-data-file-' . $stripped_email . '-' . $obscura;
     1910        $html_report_filename = $file_basename . '.html';
     1911        $html_report_pathname = $exports_dir . $html_report_filename;
     1912        $file = fopen( $html_report_pathname, 'w' );
     1913        if ( false === $file ) {
     1914                wp_send_json_error( __( 'Unable to open export file (HTML report) for writing' ) );
     1915        }
     1916
     1917        $title = sprintf(
     1918                /* translators: %s: user's e-mail address */
     1919                __( 'Personal Data Export for %s' ),
     1920                $email_address
     1921        );
     1922
     1923        // Open HTML.
     1924        fwrite( $file, "<!DOCTYPE html>\n" );
     1925        fwrite( $file, "<html>\n" );
     1926
     1927        // Head.
     1928        fwrite( $file, "<head>\n" );
     1929        fwrite( $file, "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />\n" );
     1930        fwrite( $file, "<style type='text/css'>" );
     1931        fwrite( $file, "body { color: black; font-family: Arial, sans-serif; font-size: 11pt; margin: 15px auto; width: 860px; }" );
     1932        fwrite( $file, "table { background: #f0f0f0; border: 1px solid #ddd; margin-bottom: 20px; width: 100%; }" );
     1933        fwrite( $file, "th { padding: 5px; text-align: left; width: 20%; }" );
     1934        fwrite( $file, "td { padding: 5px; }" );
     1935        fwrite( $file, "tr:nth-child(odd) { background-color: #fafafa; }" );
     1936        fwrite( $file, "</style>" );
     1937        fwrite( $file, "<title>" );
     1938        fwrite( $file, esc_html( $title ) );
     1939        fwrite( $file, "</title>" );
     1940        fwrite( $file, "</head>\n" );
     1941
     1942        // Body.
     1943        fwrite( $file, "<body>\n" );
     1944
     1945        // Heading.
     1946        fwrite( $file, "<h1>" . esc_html__( 'Personal Data Export' ) . "</h1>" );
     1947
     1948        // And now, all the Groups.
     1949        $groups = get_post_meta( $request_id, '_export_data_grouped', true );
     1950
     1951        // First, build an "About" group on the fly for this report.
     1952        $about_group = array(
     1953                'group_label' => __( 'About' ),
     1954                'items'       => array(
     1955                        'about-1' => array(
     1956                                array(
     1957                                        'name'  => __( 'Report generated for' ),
     1958                                        'value' => $email_address,
     1959                                ),
     1960                                array(
     1961                                        'name'  => __( 'For site' ),
     1962                                        'value' => get_bloginfo( 'name' ),
     1963                                ),
     1964                                array(
     1965                                        'name'  => __( 'At URL' ),
     1966                                        'value' => get_bloginfo( 'url' ),
     1967                                ),
     1968                                array(
     1969                                        'name'  => __( 'On' ),
     1970                                        'value' => current_time( 'mysql' ),
     1971                                ),
     1972                        ),
     1973                ),
     1974        );
     1975
     1976        // Merge in the special about group.
     1977        $groups = array_merge( array( 'about' => $about_group ), $groups );
     1978
     1979        // Now, iterate over every group in $groups and have the formatter render it in HTML.
     1980        foreach ( (array) $groups as $group_id => $group_data ) {
     1981                fwrite( $file, wp_privacy_generate_personal_data_export_group_html( $group_data ) );
     1982        }
     1983
     1984        fwrite( $file, "</body>\n" );
     1985
     1986        // Close HTML.
     1987        fwrite( $file, "</html>\n" );
     1988        fclose( $file );
     1989
     1990        // Now, generate the ZIP.
     1991        $archive_filename = $file_basename . '.zip';
     1992        $archive_pathname = $exports_dir . $archive_filename;
     1993        $archive_url      = $exports_url . $archive_filename;
     1994
     1995        $zip = new ZipArchive;
     1996
     1997        if ( TRUE === $zip->open( $archive_pathname, ZipArchive::CREATE ) ) {
     1998                $zip->addFile( $html_report_pathname, 'index.html' );
     1999                $zip->close();
     2000        } else {
     2001                wp_send_json_error( __( 'Unable to open export file (archive) for writing' ) );
     2002        }
     2003
     2004        // And remove the HTML file.
     2005        unlink( $html_report_pathname );
     2006
     2007        // Save the export file in the request.
     2008        update_post_meta( $request_id, '_export_file_url', $archive_url );
     2009        update_post_meta( $request_id, '_export_file_path', $archive_pathname );
     2010}
     2011
     2012/**
     2013 * Send an email to the user with a link to the personal data export file
     2014 *
     2015 * @since 4.9.6
     2016 *
     2017 * @param int  $request_id  The request ID for this personal data export.
     2018 * @return true|WP_Error    True on success or `WP_Error` on failure.
     2019 */
     2020function wp_privacy_send_personal_data_export_email( $request_id ) {
     2021        // Get the request data.
     2022        $request = wp_get_user_request_data( $request_id );
     2023
     2024        if ( ! $request || 'export_personal_data' !== $request->action_name ) {
     2025                return new WP_Error( 'invalid', __( 'Invalid request ID when sending personal data export email.' ) );
     2026        }
     2027
     2028/* translators: Do not translate LINK, EMAIL, SITENAME, SITEURL: those are placeholders. */
     2029$email_text = __(
     2030'Howdy,
     2031
     2032Your request for an export of personal data has been completed. You may
     2033download your personal data by clicking on the link below. This link is
     2034good for the next 3 days.
     2035
     2036###LINK###
     2037
     2038This email has been sent to ###EMAIL###.
     2039
     2040Regards,
     2041All at ###SITENAME###
     2042###SITEURL###'
     2043);
     2044
     2045        /**
     2046         * Filters the text of the email sent with a personal data export file.
     2047         *
     2048         * The following strings have a special meaning and will get replaced dynamically:
     2049         * ###LINK###               URL of the personal data export file for the user.
     2050         * ###EMAIL###              The email we are sending to.
     2051         * ###SITENAME###           The name of the site.
     2052         * ###SITEURL###            The URL to the site.
     2053         *
     2054         * @since 4.9.6
     2055         *
     2056         * @param string $email_text     Text in the email.
     2057         * @param int    $request_id     The request ID for this personal data export.
     2058         */
     2059        $content = apply_filters( 'wp_privacy_personal_data_email_content', $email_text, $request_id );
     2060
     2061        $email_address = $request->email;
     2062        $export_file_url = get_post_meta( $request_id, '_export_file_url', true );
     2063        $site_name = is_multisite() ? get_site_option( 'site_name' ) : get_option( 'blogname' );
     2064        $site_url = network_home_url();
     2065
     2066        $content = str_replace( '###LINK###', esc_url_raw( $export_file_url ), $content );
     2067        $content = str_replace( '###EMAIL###', $email_address, $content );
     2068        $content = str_replace( '###SITENAME###', wp_specialchars_decode( $site_name, ENT_QUOTES ), $content );
     2069        $content = str_replace( '###SITEURL###', esc_url_raw( $site_url ), $content );
     2070
     2071        $mail_success = wp_mail(
     2072                $email_address,
     2073                sprintf(
     2074                        __( '[%s] Personal Data Export' ),
     2075                        wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES )
     2076                ),
     2077                $content
     2078        );
     2079
     2080        if ( ! $mail_success ) {
     2081                return new WP_Error( 'error', __( 'Unable to send personal data export email.' ) );
     2082        }
     2083
     2084        return true;
     2085}
     2086
     2087/**
     2088 * Intercept personal data exporter page ajax responses in order to assemble the personal data export file.
     2089 * @see wp_privacy_personal_data_export_page
     2090 * @since 4.9.6
     2091 *
     2092 * @param array  $response        The response from the personal data exporter for the given page.
     2093 * @param int    $exporter_index  The index of the personal data exporter. Begins at 1.
     2094 * @param string $email_address   The email address of the user whose personal data this is.
     2095 * @param int    $page            The page of personal data for this exporter. Begins at 1.
     2096 * @param int    $request_id      The request ID for this personal data export.
     2097 * @param bool   $send_as_email   Whether the final results of the export should be emailed to the user.
     2098 * @return array The filtered response.
     2099 */
     2100function wp_privacy_process_personal_data_export_page( $response, $exporter_index, $email_address, $page, $request_id, $send_as_email ) {
     2101        /* Do some simple checks on the shape of the response from the exporter.
     2102         * If the exporter response is malformed, don't attempt to consume it - let it
     2103         * pass through to generate a warning to the user by default ajax processing.
     2104         */
     2105        if ( ! is_array( $response ) ) {
     2106                return $response;
     2107        }
     2108
     2109        if ( ! array_key_exists( 'done', $response ) ) {
     2110                return $response;
     2111        }
     2112
     2113        if ( ! array_key_exists( 'data', $response ) ) {
     2114                return $response;
     2115        }
     2116
     2117        if ( ! is_array( $response['data'] ) ) {
     2118                return $response;
     2119        }
     2120
     2121        // Get the request data.
     2122        $request = wp_get_user_request_data( $request_id );
     2123
     2124        if ( ! $request || 'export_personal_data' !== $request->action_name ) {
     2125                wp_send_json_error( __( 'Invalid request ID when merging exporter data' ) );
     2126        }
     2127
     2128        $export_data = array();
     2129
     2130        // First exporter, first page? Reset the report data accumulation array.
     2131        if ( 1 === $exporter_index && 1 === $page ) {
     2132                update_post_meta( $request_id, '_export_data_raw', $export_data );
     2133        } else {
     2134                $export_data = get_post_meta( $request_id, '_export_data_raw', true );
     2135        }
     2136
     2137        // Now, merge the data from the exporter response into the data we have accumulated already.
     2138        $export_data = array_merge( $export_data, $response['data'] );
     2139        update_post_meta( $request_id, '_export_data_raw', $export_data );
     2140
     2141        // If we are not yet on the last page of the last exporter, return now.
     2142        $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() );
     2143        $is_last_exporter = $exporter_index === count( $exporters );
     2144        $exporter_done = $response['done'];
     2145        if ( ! $is_last_exporter || ! $exporter_done ) {
     2146                return $response;
     2147        }
     2148
     2149        // Last exporter, last page - let's prepare the export file.
     2150
     2151        // First we need to re-organize the raw data hierarchically in groups and items.
     2152        $groups = array();
     2153        foreach ( (array) $export_data as $export_datum ) {
     2154                $group_id    = $export_datum['group_id'];
     2155                $group_label = $export_datum['group_label'];
     2156                if ( ! array_key_exists( $group_id, $groups ) ) {
     2157                        $groups[ $group_id ] = array(
     2158                                'group_label' => $group_label,
     2159                                'items'       => array(),
     2160                        );
     2161                }
     2162
     2163                $item_id = $export_datum['item_id'];
     2164                if ( ! array_key_exists( $item_id, $groups[ $group_id ]['items'] ) ) {
     2165                        $groups[ $group_id ]['items'][ $item_id ] = array();
     2166                }
     2167
     2168                $old_item_data = $groups[ $group_id ]['items'][ $item_id ];
     2169                $merged_item_data = array_merge( $export_datum['data'], $old_item_data );
     2170                $groups[ $group_id ]['items'][ $item_id ] = $merged_item_data;
     2171        }
     2172
     2173        // Then save the grouped data into the request.
     2174        delete_post_meta( $request_id, '_export_data_raw' );
     2175        update_post_meta( $request_id, '_export_data_grouped', $groups );
     2176
     2177        // And now, generate the export file, cleaning up any previous file
     2178        $export_path = get_post_meta( $request_id, '_export_file_path', true );
     2179        if ( ! empty( $export_path ) ) {
     2180                delete_post_meta( $request_id, '_export_file_path' );
     2181                @unlink( $export_path );
     2182        }
     2183        delete_post_meta( $request_id, '_export_file_url' );
     2184
     2185        // Generate the export file from the collected, grouped personal data.
     2186        do_action( 'wp_privacy_personal_data_export_file', $request_id );
     2187
     2188        // Clear the grouped data now that it is no longer needed.
     2189        delete_post_meta( $request_id, '_export_data_grouped' );
     2190
     2191        // If the destination is email, send it now.
     2192        if ( $send_as_email ) {
     2193                $mail_success = wp_privacy_send_personal_data_export_email( $request_id );
     2194                if ( is_wp_error( $mail_success ) ) {
     2195                        wp_send_json_error( $mail_success->get_error_message() );
     2196                }
     2197        } else {
     2198                // Modify the response to include the URL of the export file so the browser can fetch it.
     2199                $export_file_url = get_post_meta( $request_id, '_export_file_url', true );
     2200                if ( ! empty( $export_file_url ) ) {
     2201                        $response['url'] = $export_file_url;
     2202                }
     2203        }
     2204
     2205        // Update the request to completed state.
     2206        _wp_privacy_completed_request( $request_id );
     2207
     2208        return $response;
     2209}
     2210
     2211/**
     2212 * Cleans up export files older than three days old.
     2213 *
     2214 * @since 4.9.6
     2215 */
     2216function wp_privacy_delete_old_export_files() {
     2217        $upload_dir   = wp_upload_dir();
     2218        $exports_dir  = trailingslashit( $upload_dir['basedir'] . '/exports' );
     2219        $export_files = list_files( $exports_dir );
     2220
     2221        foreach( (array) $export_files as $export_file ) {
     2222                $file_age_in_seconds = time() - filemtime( $export_file );
     2223
     2224                if ( 3 * DAY_IN_SECONDS < $file_age_in_seconds ) {
     2225                        @unlink( $export_file );
     2226                }
     2227        }
     2228}
  • branches/4.9/src/wp-admin/includes/user.php

    r43086 r43092  
    620620                                'privacy_action_email_retry',
    621621                                __( 'Confirmation request re-resent successfully.' ),
    622                                 'updated'
    623                         );
    624                 }
    625 
    626         } elseif ( isset( $_POST['export_personal_data_email_send'] ) ) { // WPCS: input var ok.
    627                 check_admin_referer( 'bulk-privacy_requests' );
    628 
    629                 $request_id = absint( current( array_keys( (array) wp_unslash( $_POST['export_personal_data_email_send'] ) ) ) ); // WPCS: input var ok, sanitization ok.
    630                 $result     = false;
    631 
    632                 /**
    633                  * TODO: Email the data to the user here.
    634                  */
    635 
    636                 if ( is_wp_error( $result ) ) {
    637                         add_settings_error(
    638                                 'export_personal_data_email_send',
    639                                 'export_personal_data_email_send',
    640                                 $result->get_error_message(),
    641                                 'error'
    642                         );
    643                 } else {
    644                         _wp_privacy_completed_request( $request_id );
    645                         add_settings_error(
    646                                 'export_personal_data_email_send',
    647                                 'export_personal_data_email_send',
    648                                 __( 'Personal data was sent to the user successfully.' ),
    649622                                'updated'
    650623                        );
     
    779752        _wp_personal_data_cleanup_requests();
    780753
     754        // "Borrow" xfn.js for now so we don't have to create new files.
     755        wp_enqueue_script( 'xfn' );
     756
    781757        $requests_table = new WP_Privacy_Data_Export_Requests_Table( array(
    782758                'plural'   => 'privacy_requests',
     
    13211297                $nonce           = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id );
    13221298
    1323                 $download_data_markup = '<div class="download_personal_data" ' .
     1299                $download_data_markup = '<div class="export_personal_data" ' .
    13241300                        'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' .
    13251301                        'data-request-id="' . esc_attr( $request_id ) . '" ' .
     
    13271303                        '">';
    13281304
    1329                 $download_data_markup .= '<span class="download_personal_data_idle"><a href="#" >' . __( 'Download Personal Data' ) . '</a></span>' .
    1330                         '<span style="display:none" class="download_personal_data_processing" >' . __( 'Downloading Data...' ) . '</span>' .
    1331                         '<span style="display:none" class="download_personal_data_failed">' . __( 'Download Failed!' ) . ' <a href="#" >' . __( 'Retry' ) . '</a></span>';
     1305                $download_data_markup .= '<span class="export_personal_data_idle"><a href="#" >' . __( 'Download Personal Data' ) . '</a></span>' .
     1306                        '<span style="display:none" class="export_personal_data_processing" >' . __( 'Downloading Data...' ) . '</span>' .
     1307                        '<span style="display:none" class="export_personal_data_success"><a href="#" >' . __( 'Download Personal Data Again' ) . '</a></span>' .
     1308                        '<span style="display:none" class="export_personal_data_failed">' . __( 'Download Failed!' ) . ' <a href="#" >' . __( 'Retry' ) . '</a></span>';
     1309
     1310                $download_data_markup .= '</div>';
    13321311
    13331312                $row_actions = array(
     
    13531332                                break;
    13541333                        case 'request-confirmed':
    1355                                 // TODO Complete in follow on patch.
     1334                                $exporters       = apply_filters( 'wp_privacy_personal_data_exporters', array() );
     1335                                $exporters_count = count( $exporters );
     1336                                $request_id      = $item->ID;
     1337                                $nonce           = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id );
     1338
     1339                                echo '<div class="export_personal_data" ' .
     1340                                        'data-send-as-email="1" ' .
     1341                                        'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' .
     1342                                        'data-request-id="' . esc_attr( $request_id ) . '" ' .
     1343                                        'data-nonce="' . esc_attr( $nonce ) .
     1344                                        '">';
     1345
     1346                                ?>
     1347                                <span class="export_personal_data_idle"><a class="button" href="#" ><?php _e( 'Email Data' ); ?></a></span>
     1348                                <span style="display:none" class="export_personal_data_processing button updating-message" ><?php _e( 'Sending Email...' ); ?></span>
     1349                                <span style="display:none" class="export_personal_data_success success-message" ><?php _e( 'Email Sent!' ); ?></span>
     1350                                <span style="display:none" class="export_personal_data_failed"><?php _e( 'Email Failed!' ); ?> <a class="button" href="#" ><?php _e( 'Retry' ); ?></a></span>
     1351                                <?php
     1352
     1353                                echo '</div>';
    13561354                                break;
    13571355                        case 'request-failed':
     
    14211419                                '<span style="display:none" class="remove_personal_data_failed">' . __( 'Force Remove Failed!' ) . ' <a href="#" >' . __( 'Retry' ) . '</a></span>';
    14221420
     1421                        $remove_data_markup .= '</div>';
     1422
    14231423                        $row_actions = array(
    14241424                                'remove_data' => $remove_data_markup,
     
    14611461                                <span style="display:none" class="remove_personal_data_failed"><?php _e( 'Removing Data Failed!' ); ?> <a class="button" href="#" ><?php _e( 'Retry' ); ?></a></span>
    14621462                                <?php
     1463
     1464                                echo '</div>';
    14631465
    14641466                                break;
  • branches/4.9/src/wp-admin/js/xfn.js

    r43080 r43092  
    1818
    1919// Privacy request action handling
    20 
    2120jQuery( document ).ready( function( $ ) {
    2221        var strings = window.privacyToolsL10n || {};
     
    3534        function appendResultsAfterRow( $requestRow, classes, summaryMessage, additionalMessages ) {
    3635                clearResultsAfterRow( $requestRow );
     36
     37                var itemList = '';
    3738                if ( additionalMessages.length ) {
    38                         // TODO - render additionalMessages after the summaryMessage
     39                        $.each( additionalMessages, function( index, value ) {
     40                                itemList = itemList + '<li>' + value + '</li>';
     41                        } );
     42                        itemList = '<ul>' + itemList + '</ul>';
    3943                }
    4044
    4145                $requestRow.after( function() {
    42                         return '<tr class="request-results"><td colspan="5"><div class="notice inline notice-alt ' + classes + '"><p>' +
    43                                 summaryMessage +
    44                                 '</p></div></td></tr>';
     46                        return '<tr class="request-results"><td colspan="5">' +
     47                                '<div class="notice inline notice-alt ' + classes + '">' +
     48                                '<p>' + summaryMessage + '</p>' +
     49                                itemList +
     50                                '</div>' +
     51                                '</td>' +
     52                                '</tr>';
    4553                } );
    4654        }
     55
     56        $( '.export_personal_data a' ).click( function( event ) {
     57                event.preventDefault();
     58                event.stopPropagation();
     59
     60                var $this          = $( this );
     61                var $action        = $this.parents( '.export_personal_data' );
     62                var $requestRow    = $this.parents( 'tr' );
     63                var requestID      = $action.data( 'request-id' );
     64                var nonce          = $action.data( 'nonce' );
     65                var exportersCount = $action.data( 'exporters-count' );
     66                var sendAsEmail    = $action.data( 'send-as-email' ) ? true : false;
     67
     68                $action.blur();
     69                clearResultsAfterRow( $requestRow );
     70
     71                function on_export_done_success( zipUrl ) {
     72                        set_action_state( $action, 'export_personal_data_success' );
     73                        if ( 'undefined' !== typeof zipUrl ) {
     74                                window.location = zipUrl;
     75                        } else if ( ! sendAsEmail ) {
     76                                on_export_failure( strings.noExportFile );
     77                        }
     78                }
     79
     80                function on_export_failure( errorMessage ) {
     81                        set_action_state( $action, 'export_personal_data_failed' );
     82                        if ( errorMessage ) {
     83                                appendResultsAfterRow( $requestRow, 'notice-error', strings.exportError, [ errorMessage ] );
     84                        }
     85                }
     86
     87                function do_next_export( exporterIndex, pageIndex ) {
     88                        $.ajax(
     89                                {
     90                                        url: window.ajaxurl,
     91                                        data: {
     92                                                action: 'wp-privacy-export-personal-data',
     93                                                exporter: exporterIndex,
     94                                                id: requestID,
     95                                                page: pageIndex,
     96                                                security: nonce,
     97                                                sendAsEmail: sendAsEmail
     98                                        },
     99                                        method: 'post'
     100                                }
     101                        ).done( function( response ) {
     102                                if ( ! response.success ) {
     103                                        // e.g. invalid request ID
     104                                        on_export_failure( response.data );
     105                                        return;
     106                                }
     107                                var responseData = response.data;
     108                                if ( ! responseData.done ) {
     109                                        setTimeout( do_next_export( exporterIndex, pageIndex + 1 ) );
     110                                } else {
     111                                        if ( exporterIndex < exportersCount ) {
     112                                                setTimeout( do_next_export( exporterIndex + 1, 1 ) );
     113                                        } else {
     114                                                on_export_done_success( responseData.url );
     115                                        }
     116                                }
     117                        } ).fail( function( jqxhr, textStatus, error ) {
     118                                // e.g. Nonce failure
     119                                on_export_failure( error );
     120                        } );
     121                }
     122
     123                // And now, let's begin
     124                set_action_state( $action, 'export_personal_data_processing' );
     125                do_next_export( 1, 1 );
     126        } );
    47127
    48128        $( '.remove_personal_data a' ).click( function( event ) {
     
    88168                function on_erasure_failure() {
    89169                        set_action_state( $action, 'remove_personal_data_failed' );
    90                         appendResultsAfterRow( $requestRow, 'notice-error', strings.anErrorOccurred, [] );
     170                        appendResultsAfterRow( $requestRow, 'notice-error', strings.removalError, [] );
    91171                }
    92172
  • branches/4.9/src/wp-includes/comment.php

    r43080 r43092  
    32243224                                case 'comment_link':
    32253225                                        $value = get_comment_link( $comment->comment_ID );
     3226                                        $value = '<a href="' . $value . '" target="_blank" rel="noreferrer noopener">' . $value . '</a>';
    32263227                                        break;
    32273228                        }
  • branches/4.9/src/wp-includes/script-loader.php

    r43075 r43092  
    666666                                'noneRemoved'     => __( 'Personal data was found for this user but was not removed.' ),
    667667                                'someNotRemoved'  => __( 'Personal data was found for this user but some of the personal data found was not removed.' ),
    668                                 'anErrorOccurred' => __( 'An error occurred while attempting to find and remove personal data.' ),
     668                                'removalError'    => __( 'An error occurred while attempting to find and remove personal data.' ),
     669                                'noExportFile'    => __( 'No personal data export file was generated.' ),
     670                                'exportError'     => __( 'An error occurred while attempting to export personal data.' ),
    669671                        )
    670672                );
  • branches/4.9/src/wp-includes/user.php

    r43084 r43092  
    30703070 *
    30713071 * @param int $request_id Request ID to get data about.
    3072  * @return array|false
     3072 * @return WP_User_Request|false
    30733073 */
    30743074function wp_get_user_request_data( $request_id ) {
Note: See TracChangeset for help on using the changeset viewer.