Ticket #43546: 43546.8.diff
File 43546.8.diff, 30.4 KB (added by , 7 years ago) |
---|
-
src/wp-admin/includes/admin-filters.php
133 133 add_action( 'upgrader_process_complete', 'wp_update_plugins', 10, 0 ); 134 134 add_action( 'upgrader_process_complete', 'wp_update_themes', 10, 0 ); 135 135 136 // Privacy hooks 137 add_filter( 'wp_privacy_personal_data_export_page', 'wp_privacy_process_personal_data_export_page', 10, 6 ); 138 add_action( 'wp_privacy_personal_data_export_file', 'wp_privacy_generate_personal_data_export_file', 10 ); 139 136 140 // Privacy policy text changes check. 137 141 add_action( 'admin_init', array( 'WP_Privacy_Policy_Content', 'text_change_check' ), 20 ); 138 142 … … 144 148 145 149 // Stop checking for text changes after the policy page is updated. 146 150 add_action( 'post_updated', array( 'WP_Privacy_Policy_Content', '_policy_page_updated' ) ); 147 -
src/wp-admin/includes/ajax-actions.php
4327 4327 } 4328 4328 } 4329 4329 4330 /** 4331 * Ajax handler for exporting a user's personal data. 4332 * 4333 * @since 4.9.6 4334 */ 4330 4335 function wp_ajax_wp_privacy_export_personal_data() { 4331 check_ajax_referer( 'wp-privacy-export-personal-data', 'security' );4336 $request_id = (int) $_POST['id']; 4332 4337 4338 if ( empty( $request_id ) ) { 4339 wp_send_json_error( __( 'Error: Invalid request ID.' ) ); 4340 } 4341 4333 4342 if ( ! current_user_can( 'manage_options' ) ) { 4334 4343 wp_send_json_error( __( 'Error: Invalid request.' ) ); 4335 4344 } 4336 4345 4337 $email_address = sanitize_text_field( $_POST['email'] ); 4346 check_ajax_referer( 'wp-privacy-export-personal-data-' . $request_id, 'security' ); 4347 4348 // Find the request CPT. 4349 $request = get_post( $request_id ); 4350 if ( 'user_export_request' !== $request->post_type ) { 4351 wp_send_json_error( 'internal error: invalid request id' ); 4352 } 4353 4354 $email_address = get_post_meta( $request_id, '_user_email', true ); 4355 4356 if ( ! is_email( $email_address ) ) { 4357 wp_send_json_error( 'A valid email address must be given.' ); 4358 } 4359 4338 4360 $exporter_index = (int) $_POST['exporter']; 4339 4361 $page = (int) $_POST['page']; 4362 $send_as_email = isset( $_POST['sendAsEmail'] ) ? ( "true" === $_POST['sendAsEmail'] ) : false; 4340 4363 4341 4364 /** 4342 4365 * Filters the array of exporter callbacks. … … 4348 4371 * [ 4349 4372 * callback string Callable exporter that accepts an email address and 4350 4373 * 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. 4353 4376 * ] 4354 4377 * } 4355 4378 */ … … 4375 4398 wp_send_json_error( 'Page index cannot be less than one.' ); 4376 4399 } 4377 4400 4378 // Surprisingly, email addresses can contain mutli-byte characters now 4379 $email_address = trim( mb_strtolower( $email_address ) ); 4401 $exporter = $exporters[ $index ]; 4380 4402 4381 if ( ! is_email( $email_address ) ) {4382 wp_send_json_error( 'A valid email address must be given.' );4383 }4384 4385 $exporter = $exporters[ $index ];4386 4403 if ( ! is_array( $exporter ) ) { 4387 4404 wp_send_json_error( "Expected an array describing the exporter at index {$exporter_index}." ); 4388 4405 } 4406 if ( ! array_key_exists( 'exporter_friendly_name', $exporter ) ) { 4407 wp_send_json_error( "Exporter array at index {$exporter_index} does not include a friendly name." ); 4408 } 4389 4409 if ( ! array_key_exists( 'callback', $exporter ) ) { 4390 wp_send_json_error( "Exporter array at index {$exporter_index} does not include a callback." );4410 wp_send_json_error( "Exporter does not include a callback: {$exporter['exporter_friendly_name']}." ); 4391 4411 } 4392 4412 if ( ! is_callable( $exporter['callback'] ) ) { 4393 wp_send_json_error( "Exporter callback at index {$exporter_index} is not a valid callback." );4413 wp_send_json_error( "Exporter callback is not a valid callback: {$exporter['exporter_friendly_name']}." ); 4394 4414 } 4395 if ( ! array_key_exists( 'exporter_friendly_name', $exporter ) ) {4396 wp_send_json_error( "Exporter array at index {$exporter_index} does not include a friendly name." );4397 }4398 4415 4399 4416 $callback = $exporters[ $index ]['callback']; 4400 4417 $exporter_friendly_name = $exporters[ $index ]['exporter_friendly_name']; … … 4417 4434 wp_send_json_error( "Expected done (boolean) in response array from exporter: {$exporter_friendly_name}." ); 4418 4435 } 4419 4436 } else { 4420 // No exporters, so we're done 4437 // No exporters, so we're done. 4421 4438 $response = array( 4422 4439 'data' => array(), 4423 4440 'done' => true, … … 4435 4452 * @param int $exporter_index The index of the exporter that provided this data. 4436 4453 * @param string $email_address The email address associated with this personal data. 4437 4454 * @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. 4438 4457 */ 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 4440 4460 if ( is_wp_error( $response ) ) { 4441 4461 wp_send_json_error( $response ); 4442 4462 } -
src/wp-admin/includes/file.php
1934 1934 </div> 1935 1935 <?php 1936 1936 } 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 */ 1960 function 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 */ 2001 function 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. 2010 $request = get_post( $request_id ); 2011 if ( 'user_export_request' !== $request->post_type ) { 2012 wp_send_json_error( __( 'Invalid request ID when generating export file' ) ); 2013 } 2014 2015 // Create the exports folder if needed. 2016 $upload_dir = wp_upload_dir(); 2017 $exports_dir = trailingslashit( $upload_dir['basedir'] . '/exports' ); 2018 $exports_url = trailingslashit( $upload_dir['baseurl'] . '/exports' ); 2019 2020 $result = wp_mkdir_p( $exports_dir ); 2021 if ( is_wp_error( $result ) ) { 2022 wp_send_json_error( $result->get_error_message() ); 2023 } 2024 2025 // Protect export folder from browsing. 2026 $index_pathname = $exports_dir . 'index.html'; 2027 if ( ! file_exists( $index_pathname ) ) { 2028 $file = fopen( $index_pathname, 'w' ); 2029 if ( false === $file ) { 2030 wp_send_json_error( __( 'Unable to protect export folder from browsing' ) ); 2031 } 2032 fwrite( $file, 'Silence is golden.' ); 2033 fclose( $file ); 2034 } 2035 2036 // Generate a difficult to guess filename. 2037 $email_address = get_post_meta( $request_id, '_user_email', true ); 2038 if ( ! is_email( $email_address ) ) { 2039 wp_send_json_error( __( 'Invalid email address when generating export file' ) ); 2040 } 2041 2042 $stripped_email = str_replace( '@', '-at-', $email_address ); 2043 $stripped_email = sanitize_title( $stripped_email ); // slugify the email address 2044 $obscura = md5( rand() ); 2045 $file_basename = 'wp-personal-data-file-' . $stripped_email . '-' . $obscura; 2046 $html_report_filename = $file_basename . '.html'; 2047 $html_report_pathname = $exports_dir . $html_report_filename; 2048 $file = fopen( $html_report_pathname, 'w' ); 2049 if ( false === $file ) { 2050 wp_send_json_error( __( 'Unable to open export file (HTML report) for writing' ) ); 2051 } 2052 2053 $title = sprintf( 2054 // translators: %s Users e-mail address. 2055 __( 'Personal Data Export for %s' ), 2056 $email_address 2057 ); 2058 2059 // Open HTML. 2060 fwrite( $file, "<!DOCTYPE html>\n" ); 2061 fwrite( $file, "<html>\n" ); 2062 2063 // Head. 2064 fwrite( $file, "<head>\n" ); 2065 fwrite( $file, "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />\n" ); 2066 fwrite( $file, "<style type='text/css'>" ); 2067 fwrite( $file, "body { color: black; font-family: Arial, sans-serif; font-size: 11pt; margin: 15px auto; width: 860px; }" ); 2068 fwrite( $file, "table { background: #f0f0f0; border: 1px solid #ddd; margin-bottom: 20px; width: 100%; }" ); 2069 fwrite( $file, "th { padding: 5px; text-align: left; width: 20%; }" ); 2070 fwrite( $file, "td { padding: 5px; }" ); 2071 fwrite( $file, "tr:nth-child(odd) { background-color: #fafafa; }" ); 2072 fwrite( $file, "</style>" ); 2073 fwrite( $file, "<title>" ); 2074 fwrite( $file, esc_html( $title ) ); 2075 fwrite( $file, "</title>" ); 2076 fwrite( $file, "</head>\n" ); 2077 2078 // Body. 2079 fwrite( $file, "<body>\n" ); 2080 2081 // Heading. 2082 fwrite( $file, "<h1>" . esc_html__( 'Personal Data Export' ) . "</h1>" ); 2083 2084 // And now, all the Groups. 2085 $groups = get_post_meta( $request_id, '_export_data_grouped', true ); 2086 2087 // First, build an "About" group on the fly for this report. 2088 $about_group = array( 2089 'group_label' => __( 'About' ), 2090 'items' => array( 2091 'about-1' => array( 2092 array( 2093 'name' => __( 'Report generated for' ), 2094 'value' => $email_address, 2095 ), 2096 array( 2097 'name' => __( 'For site' ), 2098 'value' => get_bloginfo( 'name' ), 2099 ), 2100 array( 2101 'name' => __( 'At URL' ), 2102 'value' => get_bloginfo( 'url' ), 2103 ), 2104 array( 2105 'name' => __( 'On' ), 2106 'value' => current_time( 'mysql' ), 2107 ), 2108 ), 2109 ), 2110 ); 2111 2112 // Merge in the special about group. 2113 $groups = array_merge( array( 'about' => $about_group ), $groups ); 2114 2115 // Now, iterate over every group in $groups and have the formatter render it in HTML. 2116 foreach ( (array) $groups as $group_id => $group_data ) { 2117 fwrite( $file, wp_privacy_generate_personal_data_export_group_html( $group_data ) ); 2118 } 2119 2120 fwrite( $file, "</body>\n" ); 2121 2122 // Close HTML. 2123 fwrite( $file, "</html>\n" ); 2124 fclose( $file ); 2125 2126 // Now, generate the ZIP. 2127 $archive_filename = $file_basename . '.zip'; 2128 $archive_pathname = $exports_dir . $archive_filename; 2129 $archive_url = $exports_url . $archive_filename; 2130 2131 $zip = new ZipArchive; 2132 2133 if ( TRUE === $zip->open( $archive_pathname, ZipArchive::CREATE ) ) { 2134 $zip->addFile( $html_report_pathname, 'index.html' ); 2135 $zip->close(); 2136 } else { 2137 wp_send_json_error( __( 'Unable to open export file (archive) for writing' ) ); 2138 } 2139 2140 // And remove the HTML file. 2141 unlink( $html_report_pathname ); 2142 2143 // Save the export file in the request. 2144 update_post_meta( $request_id, '_export_file_url', $archive_url ); 2145 update_post_meta( $request_id, '_export_file_path', $archive_pathname ); 2146 } 2147 2148 /** 2149 * Send an email to the user with a link to the personal data export file 2150 * 2151 * @since 4.9.6 2152 * 2153 * @param int $request_id The request ID for this personal data export. 2154 * @return true|WP_Error True on success or `WP_Error` on failure. 2155 */ 2156 function wp_privacy_send_personal_data_export_email( $request_id ) { 2157 $request = get_post( $request_id ); 2158 2159 if ( 'user_export_request' !== $request->post_type ) { 2160 return new WP_Error( 'invalid', __( 'Invalid request ID when sending personal data export email.' ) ); 2161 } 2162 2163 /* translators: Do not translate LINK, EMAIL, SITENAME, SITEURL: those are placeholders. */ 2164 $email_text = __( 2165 'Howdy, 2166 2167 Your request for an export of personal data has been completed. You may 2168 download your personal data by clicking on the link below. This link is 2169 good for the next 3 days. 2170 2171 ###LINK### 2172 2173 This email has been sent to ###EMAIL###. 2174 2175 Regards, 2176 All at ###SITENAME### 2177 ###SITEURL###' 2178 ); 2179 2180 /** 2181 * Filters the text of the email sent with a personal data export file. 2182 * 2183 * The following strings have a special meaning and will get replaced dynamically: 2184 * ###LINK### URL of the personal data export file for the user. 2185 * ###EMAIL### The email we are sending to. 2186 * ###SITENAME### The name of the site. 2187 * ###SITEURL### The URL to the site. 2188 * 2189 * @since 4.9.6 2190 * 2191 * @param string $email_text Text in the email. 2192 * @param int $request_id The request ID for this personal data export. 2193 */ 2194 $content = apply_filters( 'wp_privacy_personal_data_email_content', $email_text, $request_id ); 2195 2196 $email_address = get_post_meta( $request_id, '_user_email', true ); 2197 $export_file_url = get_post_meta( $request_id, '_export_file_url', true ); 2198 $site_name = is_multisite() ? get_site_option( 'site_name' ) : get_option( 'blogname' ); 2199 $site_url = network_home_url(); 2200 2201 $content = str_replace( '###LINK###', esc_url_raw( $export_file_url ), $content ); 2202 $content = str_replace( '###EMAIL###', $email_address, $content ); 2203 $content = str_replace( '###SITENAME###', wp_specialchars_decode( $site_name, ENT_QUOTES ), $content ); 2204 $content = str_replace( '###SITEURL###', esc_url_raw( $site_url ), $content ); 2205 2206 $mail_success = wp_mail( 2207 $email_address, 2208 sprintf( 2209 __( '[%s] Personal Data Export' ), 2210 wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ) 2211 ), 2212 $content 2213 ); 2214 2215 if ( ! $mail_success ) { 2216 return new WP_Error( 'error', __( 'Unable to send personal data export email.' ) ); 2217 } 2218 2219 return true; 2220 } 2221 2222 /** 2223 * Intercept personal data exporter page ajax responses in order to assemble the personal data export file. 2224 * @see wp_privacy_personal_data_export_page 2225 * @since 4.9.6 2226 * 2227 * @param array $response The response from the personal data exporter for the given page. 2228 * @param int $exporter_index The index of the personal data exporter. Begins at 1. 2229 * @param string $email_address The email address of the user whose personal data this is. 2230 * @param int $page The page of personal data for this exporter. Begins at 1. 2231 * @param int $request_id The request ID for this personal data export. 2232 * @param bool $send_as_email Whether the final results of the export should be emailed to the user. 2233 * @return array The filtered response. 2234 */ 2235 function wp_privacy_process_personal_data_export_page( $response, $exporter_index, $email_address, $page, $request_id, $send_as_email ) { 2236 /* Do some simple checks on the shape of the response from the exporter. 2237 * If the exporter response is malformed, don't attempt to consume it - let it 2238 * pass through to generate a warning to the user by default ajax processing. 2239 */ 2240 if ( ! is_array( $response ) ) { 2241 return $response; 2242 } 2243 2244 if ( ! array_key_exists( 'done', $response ) ) { 2245 return $response; 2246 } 2247 2248 if ( ! array_key_exists( 'data', $response ) ) { 2249 return $response; 2250 } 2251 2252 if ( ! is_array( $response['data'] ) ) { 2253 return $response; 2254 } 2255 2256 // Get the request. 2257 $request = get_post( $request_id ); 2258 if ( 'user_export_request' !== $request->post_type ) { 2259 wp_send_json_error( __( 'Invalid request ID when merging exporter data' ) ); 2260 } 2261 2262 $export_data = array(); 2263 2264 // First exporter, first page? Reset the report data accumulation array. 2265 if ( 1 === $exporter_index && 1 === $page ) { 2266 update_post_meta( $request_id, '_export_data_raw', $export_data ); 2267 } else { 2268 $export_data = get_post_meta( $request_id, '_export_data_raw', true ); 2269 } 2270 2271 // Now, merge the data from the exporter response into the data we have accumulated already. 2272 $export_data = array_merge( $export_data, $response['data'] ); 2273 update_post_meta( $request_id, '_export_data_raw', $export_data ); 2274 2275 // If we are not yet on the last page of the last exporter, return now. 2276 $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() ); 2277 $is_last_exporter = $exporter_index === count( $exporters ); 2278 $exporter_done = $response['done']; 2279 if ( ! $is_last_exporter || ! $exporter_done ) { 2280 return $response; 2281 } 2282 2283 // Last exporter, last page - let's prepare the export file. 2284 2285 // First we need to re-organize the raw data hierarchically in groups and items. 2286 $groups = array(); 2287 foreach ( (array) $export_data as $export_datum ) { 2288 $group_id = $export_datum['group_id']; 2289 $group_label = $export_datum['group_label']; 2290 if ( ! array_key_exists( $group_id, $groups ) ) { 2291 $groups[ $group_id ] = array( 2292 'group_label' => $group_label, 2293 'items' => array(), 2294 ); 2295 } 2296 2297 $item_id = $export_datum['item_id']; 2298 if ( ! array_key_exists( $item_id, $groups[ $group_id ]['items'] ) ) { 2299 $groups[ $group_id ]['items'][ $item_id ] = array(); 2300 } 2301 2302 $old_item_data = $groups[ $group_id ]['items'][ $item_id ]; 2303 $merged_item_data = array_merge( $export_datum['data'], $old_item_data ); 2304 $groups[ $group_id ]['items'][ $item_id ] = $merged_item_data; 2305 } 2306 2307 // Then save the grouped data into the request. 2308 delete_post_meta( $request_id, '_export_data_raw' ); 2309 update_post_meta( $request_id, '_export_data_grouped', $groups ); 2310 2311 // And now, generate the export file. 2312 $report_path = get_post_meta( $request_id, '_export_file_path', true ); 2313 if ( ! empty( $request_path ) ) { 2314 delete_post_meta( $request_id, '_export_file_path' ); 2315 @unlink( $request_path ); 2316 } 2317 delete_post_meta( $request_id, '_export_file_url' ); 2318 2319 // Generate the export file from the collected, grouped personal data. 2320 do_action( 'wp_privacy_personal_data_export_file', $request_id ); 2321 2322 // Clear the grouped data now that it is no longer needed. 2323 delete_post_meta( $request_id, '_export_data_grouped' ); 2324 2325 // If the destination is email, send it now. 2326 if ( $send_as_email ) { 2327 $mail_success = wp_privacy_send_personal_data_export_email( $request_id ); 2328 if ( is_wp_error( $mail_success ) ) { 2329 wp_send_json_error( $mail_success->get_error_message() ); 2330 } 2331 } else { 2332 // Modify the response to include the URL of the export file so the browser can fetch it. 2333 $export_file_url = get_post_meta( $request_id, '_export_file_url', true ); 2334 if ( ! empty( $export_file_url ) ) { 2335 $response['url'] = $export_file_url; 2336 } 2337 } 2338 2339 // Update the request to completed state. 2340 _wp_privacy_completed_request( $request_id ); 2341 2342 return $response; 2343 } 2344 2345 /** 2346 * Cleans up export files older than three days old. 2347 * 2348 * @since 4.9.6 2349 */ 2350 function wp_privacy_delete_old_export_files() { 2351 $upload_dir = wp_upload_dir(); 2352 $exports_dir = trailingslashit( $upload_dir['basedir'] . '/exports' ); 2353 $export_files = list_files( $exports_dir ); 2354 2355 foreach( (array) $export_files as $export_file ) { 2356 $file_age_in_seconds = time() - filemtime( $export_file ); 2357 2358 if ( 3 * DAY_IN_SECONDS < $file_age_in_seconds ) { 2359 @unlink( $export_file ); 2360 } 2361 } 2362 } -
src/wp-admin/includes/user.php
731 731 ); 732 732 } 733 733 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 761 734 } elseif ( isset( $_POST['action'] ) ) { 762 735 $action = isset( $_POST['action'] ) ? sanitize_key( wp_unslash( $_POST['action'] ) ) : ''; // WPCS: input var ok, CSRF ok. 763 736 … … 847 820 848 821 _wp_personal_data_handle_actions(); 849 822 823 // "Borrow" xfn.js for now so we don't have to create new files. 824 wp_enqueue_script( 'xfn' ); 825 850 826 $requests_table = new WP_Privacy_Data_Export_Requests_Table( array( 851 827 'plural' => 'privacy_requests', 852 828 'singular' => 'privacy_request', … … 1368 1344 $request_id = $item['request_id']; 1369 1345 $nonce = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id ); 1370 1346 1371 $download_data_markup = '<div class=" download_personal_data" ' .1347 $download_data_markup = '<div class="export_personal_data" ' . 1372 1348 'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' . 1373 1349 'data-request-id="' . esc_attr( $request_id ) . '" ' . 1374 1350 'data-nonce="' . esc_attr( $nonce ) . 1375 1351 '">'; 1376 1352 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>'; 1380 1357 1358 $download_data_markup .= '</div>'; 1359 1381 1360 $row_actions = array( 1382 1361 'download_data' => $download_data_markup, 1383 1362 ); … … 1400 1379 esc_html_e( 'Waiting for confirmation' ); 1401 1380 break; 1402 1381 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>'; 1404 1402 break; 1405 1403 case 'request-failed': 1406 1404 submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item['request_id'] . ']', false ); … … 1468 1466 '<span style="display:none" class="remove_personal_data_processing" >' . __( 'Removing Data...' ) . '</span>' . 1469 1467 '<span style="display:none" class="remove_personal_data_failed">' . __( 'Force Remove Failed!' ) . ' <a href="#" >' . __( 'Retry' ) . '</a></span>'; 1470 1468 1469 $remove_data_markup .= '</div>'; 1470 1471 1471 $row_actions = array( 1472 1472 'remove_data' => $remove_data_markup, 1473 1473 ); … … 1509 1509 <span style="display:none" class="remove_personal_data_failed"><?php _e( 'Removing Data Failed!' ); ?> <a class="button" href="#" ><?php _e( 'Retry' ); ?></a></span> 1510 1510 <?php 1511 1511 1512 echo '</div>'; 1513 1512 1514 break; 1513 1515 case 'request-failed': 1514 1516 submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item['request_id'] . ']', false ); -
src/wp-admin/js/xfn.js
39 39 40 40 function appendResultsAfterRow( $requestRow, classes, summaryMessage, additionalMessages ) { 41 41 clearResultsAfterRow( $requestRow ); 42 43 var itemList = ''; 42 44 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>'; 44 49 } 45 50 46 51 $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>' + 48 55 summaryMessage + 49 '</p></div></td></tr>'; 56 '</p>' + 57 itemList + 58 '</div>' + 59 '</td>' + 60 '</tr>'; 50 61 } ); 51 62 } 52 63 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 53 136 $( '.remove_personal_data a' ).click( function( event ) { 54 137 event.preventDefault(); 55 138 event.stopPropagation(); … … 92 175 93 176 function on_erasure_failure() { 94 177 set_action_state( $action, 'remove_personal_data_failed' ); 95 appendResultsAfterRow( $requestRow, 'notice-error', strings. anErrorOccurred, [] );178 appendResultsAfterRow( $requestRow, 'notice-error', strings.removalError, [] ); 96 179 } 97 180 98 181 function do_next_erasure( eraserIndex, pageIndex ) { -
src/wp-includes/comment.php
3352 3352 3353 3353 case 'comment_link': 3354 3354 $value = get_comment_link( $comment->comment_ID ); 3355 $value = '<a href="' . $value . '" target="_blank">' . $value . '</a>'; 3355 3356 break; 3356 3357 } 3357 3358 -
src/wp-includes/script-loader.php
715 715 'foundAndRemoved' => __( 'All of the personal data found for this user was removed.' ), 716 716 'noneRemoved' => __( 'Personal data was found for this user but was not removed.' ), 717 717 '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.' ), 719 721 ) 720 722 ); 721 723