diff --git a/src/wp-admin/includes/privacy-tools.php b/src/wp-admin/includes/privacy-tools.php index 4f3379bff8909..8a24dc3d74fbc 100644 --- a/src/wp-admin/includes/privacy-tools.php +++ b/src/wp-admin/includes/privacy-tools.php @@ -548,6 +548,9 @@ function wp_privacy_generate_personal_data_export_file( $request_id ) { $zip->close(); if ( ! $error ) { + // Schedule the (one-off) cleanup of this export file. + wp_schedule_delete_personal_data_export_file(); + /** * Fires right after all personal data has been written to the export file. * diff --git a/src/wp-admin/includes/upgrade.php b/src/wp-admin/includes/upgrade.php index 914113bde00d0..ae095c3df8ee2 100644 --- a/src/wp-admin/includes/upgrade.php +++ b/src/wp-admin/includes/upgrade.php @@ -890,6 +890,10 @@ function upgrade_all() { upgrade_700(); } + if ( $wp_current_db_version < 61834 ) { + upgrade_710(); + } + maybe_disable_link_manager(); maybe_disable_automattic_widgets(); @@ -2510,6 +2514,37 @@ function upgrade_700() { } } +/** + * Executes changes made in WordPress 7.1.0. + * + * @ignore + * @since 7.1.0 + * + * @global int $wp_current_db_version The old (current) database version. + */ +function upgrade_710() { + global $wp_current_db_version; + + /* + * Unschedule the legacy hourly personal data export cleanup event. + * + * Cleanup is now scheduled as a one-off event when an export file is + * generated. See wp_schedule_delete_personal_data_export_file(). + */ + if ( $wp_current_db_version < 61834 ) { + $next_scheduled = wp_next_scheduled( 'wp_privacy_delete_old_export_files' ); + + if ( $next_scheduled ) { + $schedule = wp_get_schedule( 'wp_privacy_delete_old_export_files' ); + + // Only remove the recurring event; leave any on-demand single events alone. + if ( false !== $schedule ) { + wp_unschedule_event( $next_scheduled, 'wp_privacy_delete_old_export_files' ); + } + } + } +} + /** * Executes network-level upgrade routines. * diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 4b6d9de25fa11..a8a262cd30867 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -445,7 +445,6 @@ add_filter( 'wp_privacy_personal_data_exporters', 'wp_register_media_personal_data_exporter' ); add_filter( 'wp_privacy_personal_data_exporters', 'wp_register_user_personal_data_exporter', 1 ); add_filter( 'wp_privacy_personal_data_erasers', 'wp_register_comment_personal_data_eraser' ); -add_action( 'init', 'wp_schedule_delete_old_privacy_export_files' ); add_action( 'wp_privacy_delete_old_export_files', 'wp_privacy_delete_old_export_files' ); // Cron tasks. diff --git a/src/wp-includes/deprecated.php b/src/wp-includes/deprecated.php index 14f5c24aec914..b8486819a268f 100644 --- a/src/wp-includes/deprecated.php +++ b/src/wp-includes/deprecated.php @@ -6533,3 +6533,17 @@ function wp_sanitize_script_attributes( $attributes ) { } return $attributes_string; } + +/** + * Schedules a `WP_Cron` job to delete expired export files. + * + * This function is deprecated. Cleanup is now scheduled as a one-off event when + * a personal data export file is generated, via + * {@see wp_schedule_delete_personal_data_export_file()}. + * + * @since 4.9.6 + * @deprecated 7.1.0 + */ +function wp_schedule_delete_old_privacy_export_files() { + _deprecated_function( __FUNCTION__, '7.1.0', 'wp_schedule_delete_personal_data_export_file()' ); +} diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 7d71c8c56963d..a9c48ce95ebc5 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -8457,18 +8457,37 @@ function wp_privacy_exports_url() { } /** - * Schedules a `WP_Cron` job to delete expired export files. + * Schedules a one-off `WP_Cron` job to delete expired export files. * - * @since 4.9.6 + * Called after a personal data export file is generated so that the cleanup + * runs once, shortly after the file is due to expire, rather than hourly on + * every site. + * + * @since 7.1.0 */ -function wp_schedule_delete_old_privacy_export_files() { +function wp_schedule_delete_personal_data_export_file() { if ( wp_installing() ) { return; } - if ( ! wp_next_scheduled( 'wp_privacy_delete_old_export_files' ) ) { - wp_schedule_event( time(), 'hourly', 'wp_privacy_delete_old_export_files' ); + /** This filter is documented in wp-includes/functions.php */ + $expiration = apply_filters( 'wp_privacy_export_expiration', 3 * DAY_IN_SECONDS ); + + // Run shortly after the file is eligible for deletion. + $run_at = time() + (int) $expiration + MINUTE_IN_SECONDS; + + $next_scheduled = wp_next_scheduled( 'wp_privacy_delete_old_export_files' ); + + // Avoid stacking events; keep the earliest one that covers this file. + if ( $next_scheduled && $next_scheduled <= $run_at ) { + return; + } + + if ( $next_scheduled ) { + wp_unschedule_event( $next_scheduled, 'wp_privacy_delete_old_export_files' ); } + + wp_schedule_single_event( $run_at, 'wp_privacy_delete_old_export_files' ); } /** @@ -8481,6 +8500,7 @@ function wp_schedule_delete_old_privacy_export_files() { * layer of protection. * * @since 4.9.6 + * @since 7.1.0 Reschedules a follow-up one-off cleanup if any unexpired files remain. */ function wp_privacy_delete_old_export_files() { $exports_dir = wp_privacy_exports_dir(); @@ -8503,12 +8523,28 @@ function wp_privacy_delete_old_export_files() { */ $expiration = apply_filters( 'wp_privacy_export_expiration', 3 * DAY_IN_SECONDS ); + $now = time(); + $next_expiration_time = 0; + foreach ( (array) $export_files as $export_file ) { - $file_age_in_seconds = time() - filemtime( $export_file ); + $file_mtime = filemtime( $export_file ); + $file_age_in_seconds = $now - $file_mtime; if ( $expiration < $file_age_in_seconds ) { unlink( $export_file ); + continue; } + + // Track the earliest expiration of files that survived this pass. + $file_expires_at = $file_mtime + (int) $expiration; + if ( 0 === $next_expiration_time || $file_expires_at < $next_expiration_time ) { + $next_expiration_time = $file_expires_at; + } + } + + // If any non-expired files remain, schedule a follow-up cleanup for them. + if ( $next_expiration_time > $now && ! wp_next_scheduled( 'wp_privacy_delete_old_export_files' ) ) { + wp_schedule_single_event( $next_expiration_time + MINUTE_IN_SECONDS, 'wp_privacy_delete_old_export_files' ); } } diff --git a/src/wp-includes/version.php b/src/wp-includes/version.php index 934e5d0bb5369..364e97f70ce2a 100644 --- a/src/wp-includes/version.php +++ b/src/wp-includes/version.php @@ -23,7 +23,7 @@ * * @global int $wp_db_version */ -$wp_db_version = 61833; +$wp_db_version = 61834; /** * Holds the TinyMCE version.