Ticket #43546: 43546.12.diff
File 43546.12.diff, 15.8 KB (added by , 7 years ago) |
---|
-
src/wp-admin/includes/file.php
diff --git src/wp-admin/includes/file.php src/wp-admin/includes/file.php index 8c8d6bc8d2..64cacd03df 100644
function wp_print_request_filesystem_credentials_modal() { 1959 1959 */ 1960 1960 function wp_privacy_generate_personal_data_export_group_html( $group_data ) { 1961 1961 $allowed_tags = array( 1962 'a' => array(1962 'a' => array( 1963 1963 'href' => array(), 1964 'target' => array() 1964 'target' => array(), 1965 1965 ), 1966 'br' => array() 1966 'br' => array(), 1967 1967 ); 1968 1968 $allowed_protocols = array( 'http', 'https' ); 1969 1969 $group_html = ''; … … function wp_privacy_generate_personal_data_export_group_html( $group_data ) { 1999 1999 * @param int $request_id The export request ID. 2000 2000 */ 2001 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 2002 if ( ! class_exists( 'ZipArchive' ) ) { 2006 2003 wp_send_json_error( __( 'Unable to generate export file. ZipArchive not available.' ) ); 2007 2004 } … … function wp_privacy_generate_personal_data_export_file( $request_id ) { 2042 2039 2043 2040 $stripped_email = str_replace( '@', '-at-', $email_address ); 2044 2041 $stripped_email = sanitize_title( $stripped_email ); // slugify the email address 2045 $obscura = md5( rand());2042 $obscura = wp_generate_password( 32, false, false ); 2046 2043 $file_basename = 'wp-personal-data-file-' . $stripped_email . '-' . $obscura; 2047 2044 $html_report_filename = $file_basename . '.html'; 2048 2045 $html_report_pathname = $exports_dir . $html_report_filename; 2049 $file = fopen( $html_report_pathname, 'w' ); 2046 $file = fopen( $html_report_pathname, 'w' ); 2047 2050 2048 if ( false === $file ) { 2051 2049 wp_send_json_error( __( 'Unable to open export file (HTML report) for writing' ) ); 2052 2050 } … … function wp_privacy_generate_personal_data_export_file( $request_id ) { 2125 2123 fclose( $file ); 2126 2124 2127 2125 // Now, generate the ZIP. 2126 $error = false; 2128 2127 $archive_filename = $file_basename . '.zip'; 2129 2128 $archive_pathname = $exports_dir . $archive_filename; 2130 2129 $archive_url = $exports_url . $archive_filename; 2130 $zip = new ZipArchive; 2131 2131 2132 $zip = new ZipArchive; 2132 if ( true === $zip->open( $archive_pathname, ZipArchive::CREATE ) ) { 2133 if ( ! $zip->addFile( $html_report_pathname, 'index.html' ) ) { 2134 $error = __( 'Unable to add data to export file.' ); 2135 } 2133 2136 2134 if ( TRUE === $zip->open( $archive_pathname, ZipArchive::CREATE ) ) {2135 $zip->addFile( $html_report_pathname, 'index.html' );2136 2137 $zip->close(); 2138 2139 if ( ! $error ) { 2140 /** 2141 * Fires right after all personal data has been written to the export file. 2142 * 2143 * @since 4.9.6 2144 * 2145 * @param string $archive_pathname The full path to the export file on the filesystem. 2146 * @param string $archive_url The URL of the archive file. 2147 * @param string $html_report_pathname The full path to the personal data report on the filesystem. 2148 */ 2149 do_action( 'wp_privacy_personal_data_export_file_created', $archive_pathname, $archive_url, $html_report_pathname ); 2150 } 2137 2151 } else { 2138 wp_send_json_error( __( 'Unable to open export file (archive) for writing' ));2152 $error = __( 'Unable to open export file (archive) for writing.' ); 2139 2153 } 2140 2154 2141 2155 // And remove the HTML file. 2142 2156 unlink( $html_report_pathname ); 2143 2157 2158 if ( $error ) { 2159 wp_send_json_error( $error ); 2160 } 2161 2144 2162 // Save the export file in the request. 2145 2163 update_post_meta( $request_id, '_export_file_url', $archive_url ); 2146 2164 update_post_meta( $request_id, '_export_file_path', $archive_pathname ); … … function wp_privacy_send_personal_data_export_email( $request_id ) { 2162 2180 return new WP_Error( 'invalid', __( 'Invalid request ID when sending personal data export email.' ) ); 2163 2181 } 2164 2182 2165 /* translators: Do not translate LINK, EMAIL, SITENAME, SITEURL: those are placeholders.*/2166 $email_text = __( 2167 'Howdy, 2183 /** This filter is documented in wp-admin/includes/file.php */ 2184 $expiration = apply_filters( 'wp_privacy_export_expiration', 3 * DAY_IN_SECONDS ); 2185 $expiration_date = date_i18n( get_option( 'date_format' ), time() + $expiration ); 2168 2186 2169 Your request for an export of personal data has been completed. You may 2170 download your personal data by clicking on the link below. This link is 2171 good for the next 3 days. 2187 /* translators: Do not translate EXPIRATION, LINK, EMAIL, SITENAME, SITEURL: those are placeholders. */ 2188 $email_text = str_replace( "\t", '', __( 2189 'Howdy, 2172 2190 2173 ###LINK### 2191 Your request for an export of personal data has been completed. You may 2192 download your personal data by clicking on the link below. For privacy 2193 and security, we will automatically delete the file on ###EXPIRATION###, 2194 so please download it before then. 2174 2195 2175 This email has been sent to ###EMAIL###. 2196 ###LINK### 2176 2197 2177 Regards, 2178 All at ###SITENAME### 2179 ###SITEURL###' 2180 ); 2198 This email has been sent to ###EMAIL###. 2199 2200 Regards, 2201 All at ###SITENAME### 2202 ###SITEURL###' 2203 ) ); 2181 2204 2182 2205 /** 2183 2206 * Filters the text of the email sent with a personal data export file. … … All at ###SITENAME### 2193 2216 * @param string $email_text Text in the email. 2194 2217 * @param int $request_id The request ID for this personal data export. 2195 2218 */ 2196 $content = apply_filters( 'wp_privacy_personal_data_email_content', $email_text, $request_id ); 2197 2198 $email_address = $request->email; 2219 $content = apply_filters( 'wp_privacy_personal_data_email_content', $email_text, $request_id ); 2220 $email_address = $request->email; 2199 2221 $export_file_url = get_post_meta( $request_id, '_export_file_url', true ); 2200 $site_name = is_multisite() ? get_site_option( 'site_name' ) : get_option( 'blogname' );2201 $site_url = network_home_url();2222 $site_name = is_multisite() ? get_site_option( 'site_name' ) : get_option( 'blogname' ); 2223 $site_url = network_home_url(); 2202 2224 2225 $content = str_replace( '###EXPIRATION###', $expiration_date, $content ); 2203 2226 $content = str_replace( '###LINK###', esc_url_raw( $export_file_url ), $content ); 2204 2227 $content = str_replace( '###EMAIL###', $email_address, $content ); 2205 2228 $content = str_replace( '###SITENAME###', wp_specialchars_decode( $site_name, ENT_QUOTES ), $content ); … … All at ###SITENAME### 2208 2231 $mail_success = wp_mail( 2209 2232 $email_address, 2210 2233 sprintf( 2234 // translators: %s is the name of the site. 2211 2235 __( '[%s] Personal Data Export' ), 2212 2236 wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ) 2213 2237 ), … … All at ###SITENAME### 2223 2247 2224 2248 /** 2225 2249 * Intercept personal data exporter page ajax responses in order to assemble the personal data export file. 2226 * @see wp_privacy_personal_data_export_page2250 * 2227 2251 * @since 4.9.6 2228 2252 * 2253 * @see wp_privacy_personal_data_export_page 2254 * 2229 2255 * @param array $response The response from the personal data exporter for the given page. 2230 2256 * @param int $exporter_index The index of the personal data exporter. Begins at 1. 2231 2257 * @param string $email_address The email address of the user whose personal data this is. … … function wp_privacy_process_personal_data_export_page( $response, $exporter_inde 2276 2302 update_post_meta( $request_id, '_export_data_raw', $export_data ); 2277 2303 2278 2304 // If we are not yet on the last page of the last exporter, return now. 2279 $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() ); 2280 $is_last_exporter = $exporter_index === count( $exporters ); 2281 $exporter_done = $response['done']; 2305 $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() ); 2306 $is_last_exporter = count( $exporters ) === $exporter_index; 2307 $exporter_done = $response['done']; 2308 2282 2309 if ( ! $is_last_exporter || ! $exporter_done ) { 2283 2310 return $response; 2284 2311 } … … function wp_privacy_process_personal_data_export_page( $response, $exporter_inde 2302 2329 $groups[ $group_id ]['items'][ $item_id ] = array(); 2303 2330 } 2304 2331 2305 $old_item_data = $groups[ $group_id ]['items'][ $item_id ];2306 $merged_item_data = array_merge( $export_datum['data'], $old_item_data );2332 $old_item_data = $groups[ $group_id ]['items'][ $item_id ]; 2333 $merged_item_data = array_merge( $export_datum['data'], $old_item_data ); 2307 2334 $groups[ $group_id ]['items'][ $item_id ] = $merged_item_data; 2308 2335 } 2309 2336 … … function wp_privacy_process_personal_data_export_page( $response, $exporter_inde 2344 2371 2345 2372 return $response; 2346 2373 } 2347 2348 /**2349 * Cleans up export files older than three days old.2350 *2351 * @since 4.9.62352 */2353 function wp_privacy_delete_old_export_files() {2354 $upload_dir = wp_upload_dir();2355 $exports_dir = trailingslashit( $upload_dir['basedir'] . '/exports' );2356 $export_files = list_files( $exports_dir );2357 2358 foreach( (array) $export_files as $export_file ) {2359 $file_age_in_seconds = time() - filemtime( $export_file );2360 2361 if ( 3 * DAY_IN_SECONDS < $file_age_in_seconds ) {2362 @unlink( $export_file );2363 }2364 }2365 } -
src/wp-includes/default-filters.php
diff --git src/wp-includes/default-filters.php src/wp-includes/default-filters.php index dcdebacb1d..a3c68eed86 100644
add_action( 'user_request_action_confirmed', '_wp_privacy_account_request_confir 352 352 add_filter( 'user_request_action_confirmed_message', '_wp_privacy_account_request_confirmed_message', 10, 2 ); 353 353 add_filter( 'wp_privacy_personal_data_exporters', 'wp_register_comment_personal_data_exporter' ); 354 354 add_filter( 'wp_privacy_personal_data_erasers', 'wp_register_comment_personal_data_eraser' ); 355 add_action( 'init', 'wp_schedule_delete_old_privacy_export_files' ); 356 add_action( 'wp_privacy_delete_old_export_files', 'wp_privacy_delete_old_export_files' ); 355 357 356 358 // Cron tasks 357 359 add_action( 'wp_scheduled_delete', 'wp_scheduled_delete' ); -
src/wp-includes/functions.php
diff --git src/wp-includes/functions.php src/wp-includes/functions.php index de978b3b5d..647e0db2d0 100644
function wp_privacy_anonymize_data( $type, $data = '' ) { 6257 6257 function _wp_privacy_active_plugins_change() { 6258 6258 update_option( '_wp_privacy_text_change_check', 'check' ); 6259 6259 } 6260 6261 /** 6262 * Schedule a `WP_Cron` job to delete expired export files. 6263 * 6264 * @since 4.9.6 6265 */ 6266 function wp_schedule_delete_old_privacy_export_files() { 6267 if ( ! wp_next_scheduled( 'wp_privacy_delete_old_export_files' ) ) { 6268 wp_schedule_event( time(), 'hourly', 'wp_privacy_delete_old_export_files' ); 6269 } 6270 } 6271 6272 /** 6273 * Cleans up export files older than three days old. 6274 * 6275 * The export files are stored in `wp-content/uploads`, and are therefore publicly 6276 * accessible. A CSPRN is appended to the filename to mitigate the risk of an 6277 * unauthorized person downloading the file, but it is still possible. Deleting 6278 * the file after the data subject has had a chance to delete it adds an additional 6279 * layer of protection. 6280 * 6281 * @since 4.9.6 6282 */ 6283 function wp_privacy_delete_old_export_files() { 6284 $upload_dir = wp_upload_dir(); 6285 $exports_dir = trailingslashit( $upload_dir['basedir'] . '/exports' ); 6286 $export_files = list_files( $exports_dir, 100, array( 'index.html' ) ); 6287 6288 /** 6289 * Filters the lifetime, in seconds, of a personal data export file. 6290 * 6291 * By default, the lifetime is 3 days. Once the file reaches that age, it will automatically 6292 * be deleted by a cron job. 6293 * 6294 * @since 4.9.6 6295 * 6296 * @param int $expiration The expiration age of the export, in seconds. 6297 */ 6298 $expiration = apply_filters( 'wp_privacy_export_expiration', 3 * DAY_IN_SECONDS ); 6299 6300 foreach ( (array) $export_files as $export_file ) { 6301 $file_age_in_seconds = time() - filemtime( $export_file ); 6302 6303 if ( $expiration < $file_age_in_seconds ) { 6304 unlink( $export_file ); 6305 } 6306 } 6307 } -
new file tests/phpunit/tests/privacy/Tests_Privacy_wpPrivacyDeleteOldExportFiles.php
diff --git tests/phpunit/tests/privacy/Tests_Privacy_wpPrivacyDeleteOldExportFiles.php tests/phpunit/tests/privacy/Tests_Privacy_wpPrivacyDeleteOldExportFiles.php new file mode 100644 index 0000000000..8f8cb23a6d
- + 1 <?php 2 /** 3 * Define a class to test `wp_privacy_delete_old_export_files()`. 4 * 5 * @package WordPress 6 * @subpackage UnitTests 7 * @since 4.9.6 8 */ 9 10 /** 11 * Test cases for `wp_privacy_delete_old_export_files()`. 12 * 13 * @group privacy 14 * @covers wp_privacy_delete_old_export_files 15 * 16 * @since 4.9.6 17 */ 18 class Tests_Privacy_wpPrivacyDeleteOldExportFiles extends WP_UnitTestCase { 19 /** 20 * Create fixtures that are shared by multiple test cases. 21 * 22 * @param WP_UnitTest_Factory $factory The base factory object. 23 */ 24 public static function wpSetUpBeforeClass( $factory ) { 25 26 } 27 28 /* 29 todo cases: 30 ? 31 */ 32 } -
new file tests/phpunit/tests/privacy/wpPrivacyGeneratePersonalDataExportFile.php
diff --git tests/phpunit/tests/privacy/wpPrivacyGeneratePersonalDataExportFile.php tests/phpunit/tests/privacy/wpPrivacyGeneratePersonalDataExportFile.php new file mode 100644 index 0000000000..0de255e7a5
- + 1 <?php 2 /** 3 * Define a class to test `wp_privacy_generate_personal_data_export_file()`. 4 * 5 * @package WordPress 6 * @subpackage UnitTests 7 * @since 4.9.6 8 */ 9 10 /** 11 * Test cases for `wp_privacy_generate_personal_data_export_file()`. 12 * 13 * @group privacy 14 * @covers wp_privacy_generate_personal_data_export_file 15 * 16 * @since 4.9.6 17 */ 18 class Tests_Privacy_wpPrivacyGeneratePersonalDataExportFile extends WP_UnitTestCase { 19 /** 20 * Create fixtures that are shared by multiple test cases. 21 * 22 * @param WP_UnitTest_Factory $factory The base factory object. 23 */ 24 public static function wpSetUpBeforeClass( $factory ) { 25 26 } 27 28 /* 29 todo cases: 30 ziparchive not available 31 invalid request id 32 invalid email addr 33 mkdir_p fails 34 index.html created in export folder - silence is golden 35 unable to protect error if can't create index.html file 36 unable to open export file error if can't write 37 report file exists, has starts with <html> and ends with </html> 38 unable to add data error if can't add file to export 39 error if can't open archive file 40 report file removed, even if errors 41 */ 42 } -
new file tests/phpunit/tests/privacy/wpPrivacyProcessPersonalDataExportPage.php
diff --git tests/phpunit/tests/privacy/wpPrivacyProcessPersonalDataExportPage.php tests/phpunit/tests/privacy/wpPrivacyProcessPersonalDataExportPage.php new file mode 100644 index 0000000000..7031b6f48d
- + 1 <?php 2 /** 3 * Define a class to test `wp_privacy_process_personal_data_export_page()`. 4 * 5 * @package WordPress 6 * @subpackage UnitTests 7 * @since 4.9.6 8 */ 9 10 /** 11 * Test cases for `wp_privacy_process_personal_data_export_page()`. 12 * 13 * @group privacy 14 * @covers wp_privacy_process_personal_data_export_page 15 * 16 * @since 4.9.6 17 */ 18 class Tests_Privacy_wpPrivacyProcessPersonalDataExportPage extends WP_UnitTestCase { 19 /** 20 * Create fixtures that are shared by multiple test cases. 21 * 22 * @param WP_UnitTest_Factory $factory The base factory object. 23 */ 24 public static function wpSetUpBeforeClass( $factory ) { 25 26 } 27 28 /* 29 todo cases: 30 ? 31 */ 32 } -
new file tests/phpunit/tests/privacy/wpPrivacySendPersonalDataExportEmail.php
diff --git tests/phpunit/tests/privacy/wpPrivacySendPersonalDataExportEmail.php tests/phpunit/tests/privacy/wpPrivacySendPersonalDataExportEmail.php new file mode 100644 index 0000000000..0a2dccb6c0
- + 1 <?php 2 /** 3 * Define a class to test `wp_privacy_send_personal_data_export_email()`. 4 * 5 * @package WordPress 6 * @subpackage UnitTests 7 * @since 4.9.6 8 */ 9 10 /** 11 * Test cases for `wp_privacy_send_personal_data_export_email()`. 12 * 13 * @group privacy 14 * @covers wp_privacy_send_personal_data_export_email 15 * 16 * @since 4.9.6 17 */ 18 class Tests_Privacy_wpPrivacySendPersonalDataExportEmail extends WP_UnitTestCase { 19 /** 20 * Create fixtures that are shared by multiple test cases. 21 * 22 * @param WP_UnitTest_Factory $factory The base factory object. 23 */ 24 public static function wpSetUpBeforeClass( $factory ) { 25 26 } 27 28 /* 29 todo cases: 30 ? 31 */ 32 }