Ticket #43546: 43546.9.diff
File 43546.9.diff, 30.4 KB (added by , 5 years ago) |
---|
-
src/wp-admin/includes/admin-filters.php
132 132 add_action( 'upgrader_process_complete', 'wp_update_plugins', 10, 0 ); 133 133 add_action( 'upgrader_process_complete', 'wp_update_themes', 10, 0 ); 134 134 135 // Privacy hooks 136 add_filter( 'wp_privacy_personal_data_export_page', 'wp_privacy_process_personal_data_export_page', 10, 6 ); 137 add_action( 'wp_privacy_personal_data_export_file', 'wp_privacy_generate_personal_data_export_file', 10 ); 138 135 139 // Privacy policy text changes check. 136 140 add_action( 'admin_init', array( 'WP_Privacy_Policy_Content', 'text_change_check' ), 20 ); 137 141 … … 143 147 144 148 // Stop checking for text changes after the policy page is updated. 145 149 add_action( 'post_updated', array( 'WP_Privacy_Policy_Content', '_policy_page_updated' ) ); 146 -
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_request' !== $request->post_type ) { 4351 wp_send_json_error( __( 'Error: Invalid request type' ) ); 4352 } 4353 4354 $email_address = get_post_meta( $request_id, '_wp_user_request_user_email', true ); 4355 if ( ! is_email( $email_address ) ) { 4356 wp_send_json_error( __( 'Error: A valid email address must be given.' ) ); 4357 } 4358 4338 4359 $exporter_index = (int) $_POST['exporter']; 4339 4360 $page = (int) $_POST['page']; 4361 $send_as_email = isset( $_POST['sendAsEmail'] ) ? ( "true" === $_POST['sendAsEmail'] ) : false; 4340 4362 4341 4363 /** 4342 4364 * Filters the array of exporter callbacks. … … 4348 4370 * [ 4349 4371 * callback string Callable exporter that accepts an email address and 4350 4372 * 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 4373 * pairs of personal data. 4374 * exporter_friendly_name string Translated user facing friendly name for the exporter. 4353 4375 * ] 4354 4376 * } 4355 4377 */ … … 4375 4397 wp_send_json_error( 'Page index cannot be less than one.' ); 4376 4398 } 4377 4399 4378 // Surprisingly, email addresses can contain mutli-byte characters now 4379 $email_address = trim( mb_strtolower( $email_address ) ); 4400 $exporter = $exporters[ $index ]; 4380 4401 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 4402 if ( ! is_array( $exporter ) ) { 4387 4403 wp_send_json_error( "Expected an array describing the exporter at index {$exporter_index}." ); 4388 4404 } 4405 if ( ! array_key_exists( 'exporter_friendly_name', $exporter ) ) { 4406 wp_send_json_error( "Exporter array at index {$exporter_index} does not include a friendly name." ); 4407 } 4389 4408 if ( ! array_key_exists( 'callback', $exporter ) ) { 4390 wp_send_json_error( "Exporter array at index {$exporter_index} does not include a callback." );4409 wp_send_json_error( "Exporter does not include a callback: {$exporter['exporter_friendly_name']}." ); 4391 4410 } 4392 4411 if ( ! is_callable( $exporter['callback'] ) ) { 4393 wp_send_json_error( "Exporter callback at index {$exporter_index} is not a valid callback." );4412 wp_send_json_error( "Exporter callback is not a valid callback: {$exporter['exporter_friendly_name']}." ); 4394 4413 } 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 4414 4399 4415 $callback = $exporters[ $index ]['callback']; 4400 4416 $exporter_friendly_name = $exporters[ $index ]['exporter_friendly_name']; … … 4417 4433 wp_send_json_error( "Expected done (boolean) in response array from exporter: {$exporter_friendly_name}." ); 4418 4434 } 4419 4435 } else { 4420 // No exporters, so we're done 4436 // No exporters, so we're done. 4421 4437 $response = array( 4422 4438 'data' => array(), 4423 4439 'done' => true, … … 4435 4451 * @param int $exporter_index The index of the exporter that provided this data. 4436 4452 * @param string $email_address The email address associated with this personal data. 4437 4453 * @param int $page The zero-based page for this response. 4454 * @param int $request_id The privacy request post ID associated with this request. 4455 * @param bool $send_as_email Whether the final results of the export should be emailed to the user. 4438 4456 */ 4439 $response = apply_filters( 'wp_privacy_personal_data_export_page', $response, $exporter_index, $email_address, $page ); 4457 $response = apply_filters( 'wp_privacy_personal_data_export_page', $response, $exporter_index, $email_address, $page, $request_id, $send_as_email ); 4458 4440 4459 if ( is_wp_error( $response ) ) { 4441 4460 wp_send_json_error( $response ); 4442 4461 } -
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_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, '_wp_user_request_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_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, '_wp_user_request_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_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, cleaning up any previous file 2312 $export_path = get_post_meta( $request_id, '_export_file_path', true ); 2313 if ( ! empty( $export_path ) ) { 2314 delete_post_meta( $request_id, '_export_file_path' ); 2315 @unlink( $export_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
662 662 ); 663 663 } 664 664 665 } elseif ( isset( $_POST['export_personal_data_email_send'] ) ) { // WPCS: input var ok.666 check_admin_referer( 'bulk-privacy_requests' );667 668 $request_id = absint( current( array_keys( (array) wp_unslash( $_POST['export_personal_data_email_send'] ) ) ) ); // WPCS: input var ok, sanitization ok.669 $result = false;670 671 /**672 * TODO: Email the data to the user here.673 */674 675 if ( is_wp_error( $result ) ) {676 add_settings_error(677 'export_personal_data_email_send',678 'export_personal_data_email_send',679 $result->get_error_message(),680 'error'681 );682 } else {683 _wp_privacy_completed_request( $request_id );684 add_settings_error(685 'export_personal_data_email_send',686 'export_personal_data_email_send',687 __( 'Personal data was sent to the user successfully.' ),688 'updated'689 );690 }691 692 665 } elseif ( isset( $_POST['action'] ) ) { 693 666 $action = isset( $_POST['action'] ) ? sanitize_key( wp_unslash( $_POST['action'] ) ) : ''; // WPCS: input var ok, CSRF ok. 694 667 … … 784 757 785 758 _wp_personal_data_handle_actions(); 786 759 760 // "Borrow" xfn.js for now so we don't have to create new files. 761 wp_enqueue_script( 'xfn' ); 762 787 763 $requests_table = new WP_Privacy_Data_Export_Requests_Table( array( 788 764 'plural' => 'privacy_requests', 789 765 'singular' => 'privacy_request', … … 1334 1310 $request_id = $item['request_id']; 1335 1311 $nonce = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id ); 1336 1312 1337 $download_data_markup = '<div class=" download_personal_data" ' .1313 $download_data_markup = '<div class="export_personal_data" ' . 1338 1314 'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' . 1339 1315 'data-request-id="' . esc_attr( $request_id ) . '" ' . 1340 1316 'data-nonce="' . esc_attr( $nonce ) . 1341 1317 '">'; 1342 1318 1343 $download_data_markup .= '<span class="download_personal_data_idle"><a href="#" >' . __( 'Download Personal Data' ) . '</a></span>' . 1344 '<span style="display:none" class="download_personal_data_processing" >' . __( 'Downloading Data...' ) . '</span>' . 1345 '<span style="display:none" class="download_personal_data_failed">' . __( 'Download Failed!' ) . ' <a href="#" >' . __( 'Retry' ) . '</a></span>'; 1319 $download_data_markup .= '<span class="export_personal_data_idle"><a href="#" >' . __( 'Download Personal Data' ) . '</a></span>' . 1320 '<span style="display:none" class="export_personal_data_processing" >' . __( 'Downloading Data...' ) . '</span>' . 1321 '<span style="display:none" class="export_personal_data_success"><a href="#" >' . __( 'Download Personal Data Again' ) . '</a></span>' . 1322 '<span style="display:none" class="export_personal_data_failed">' . __( 'Download Failed!' ) . ' <a href="#" >' . __( 'Retry' ) . '</a></span>'; 1346 1323 1324 $download_data_markup .= '</div>'; 1325 1347 1326 $row_actions = array( 1348 1327 'download_data' => $download_data_markup, 1349 1328 ); … … 1366 1345 esc_html_e( 'Waiting for confirmation' ); 1367 1346 break; 1368 1347 case 'request-confirmed': 1369 // TODO Complete in follow on patch. 1348 $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() ); 1349 $exporters_count = count( $exporters ); 1350 $request_id = $item['request_id']; 1351 $nonce = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id ); 1352 1353 echo '<div class="export_personal_data" ' . 1354 'data-send-as-email="1" ' . 1355 'data-exporters-count="' . esc_attr( $exporters_count ) . '" ' . 1356 'data-request-id="' . esc_attr( $request_id ) . '" ' . 1357 'data-nonce="' . esc_attr( $nonce ) . 1358 '">'; 1359 1360 ?> 1361 <span class="export_personal_data_idle"><a class="button" href="#" ><?php _e( 'Email Data' ); ?></a></span> 1362 <span style="display:none" class="export_personal_data_processing button updating-message" ><?php _e( 'Sending Email...' ); ?></span> 1363 <span style="display:none" class="export_personal_data_success success-message" ><?php _e( 'Email Sent!' ); ?></span> 1364 <span style="display:none" class="export_personal_data_failed"><?php _e( 'Email Failed!' ); ?> <a class="button" href="#" ><?php _e( 'Retry' ); ?></a></span> 1365 <?php 1366 1367 echo '</div>'; 1370 1368 break; 1371 1369 case 'request-failed': 1372 1370 submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item['request_id'] . ']', false ); … … 1434 1432 '<span style="display:none" class="remove_personal_data_processing" >' . __( 'Removing Data...' ) . '</span>' . 1435 1433 '<span style="display:none" class="remove_personal_data_failed">' . __( 'Force Remove Failed!' ) . ' <a href="#" >' . __( 'Retry' ) . '</a></span>'; 1436 1434 1435 $remove_data_markup .= '</div>'; 1436 1437 1437 $row_actions = array( 1438 1438 'remove_data' => $remove_data_markup, 1439 1439 ); … … 1475 1475 <span style="display:none" class="remove_personal_data_failed"><?php _e( 'Removing Data Failed!' ); ?> <a class="button" href="#" ><?php _e( 'Retry' ); ?></a></span> 1476 1476 <?php 1477 1477 1478 echo '</div>'; 1479 1478 1480 break; 1479 1481 case 'request-failed': 1480 1482 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