Administration: Prevent repeat emails for identical plugin or theme auto-update attempt failures.

This change adds a throttle mechanism to plugin and theme auto-update failure emails using similar logic to the email sent for a Core auto-update.

The first time a plugin or theme auto-update fails, the package and `new_version` will be tracked in the `auto_plugin_theme_update_emails` option. An email for this specific update attempt will not be resent.

However, if this update fails again and non-repeat failures or successful updates are also present, then the failure information will be included in that email (an email needs to be sent for the new events regardless).

Props johnbillion, arpitgshah, desrosj, audrasjb, pbiron, earnjam.
Fixes #50448.

git-svn-id: https://develop.svn.wordpress.org/trunk@48397 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Jonathan Desrosiers 2020-07-07 18:58:32 +00:00
parent a7dc1bbef1
commit bfb896ce08
5 changed files with 92 additions and 5 deletions

View File

@ -247,6 +247,15 @@ class Plugin_Upgrader extends WP_Upgrader {
// Force refresh of plugin update information. // Force refresh of plugin update information.
wp_clean_plugins_cache( $parsed_args['clear_update_cache'] ); wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
// Ensure any future auto-update failures trigger a failure email by removing the last
// failure notification from the list when plugins update successfully.
$past_failure_emails = get_option( 'auto_plugin_theme_update_emails', array() );
if ( isset( $past_failure_emails[ $plugin ] ) ) {
unset( $past_failure_emails[ $plugin ] );
update_option( 'auto_plugin_theme_update_emails', $past_failure_emails );
}
return true; return true;
} }
@ -370,6 +379,20 @@ class Plugin_Upgrader extends WP_Upgrader {
// Cleanup our hooks, in case something else does a upgrade on this connection. // Cleanup our hooks, in case something else does a upgrade on this connection.
remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_plugin' ) ); remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_plugin' ) );
// Ensure any future auto-update failures trigger a failure email by removing the last
// failure notification from the list when plugins update successfully.
$past_failure_emails = get_option( 'auto_plugin_theme_update_emails', array() );
foreach ( $results as $plugin => $result ) {
// Maintain last failure notification when plugins failed to update manually.
if ( ! $result || is_wp_error( $result ) || ! isset( $past_failure_emails[ $plugin ] ) ) {
continue;
}
unset( $past_failure_emails[ $plugin ] );
}
update_option( 'auto_plugin_theme_update_emails', $past_failure_emails );
return $results; return $results;
} }

View File

@ -352,6 +352,15 @@ class Theme_Upgrader extends WP_Upgrader {
wp_clean_themes_cache( $parsed_args['clear_update_cache'] ); wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
// Ensure any future auto-update failures trigger a failure email by removing the last
// failure notification from the list when themes update successfully.
$past_failure_emails = get_option( 'auto_plugin_theme_update_emails', array() );
if ( isset( $past_failure_emails[ $theme ] ) ) {
unset( $past_failure_emails[ $theme ] );
update_option( 'auto_plugin_theme_update_emails', $past_failure_emails );
}
return true; return true;
} }
@ -479,6 +488,20 @@ class Theme_Upgrader extends WP_Upgrader {
remove_filter( 'upgrader_post_install', array( $this, 'current_after' ) ); remove_filter( 'upgrader_post_install', array( $this, 'current_after' ) );
remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_theme' ) ); remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_theme' ) );
// Ensure any future auto-update failures trigger a failure email by removing the last
// failure notification from the list when themes update successfully.
$past_failure_emails = get_option( 'auto_plugin_theme_update_emails', array() );
foreach ( $results as $theme => $result ) {
// Maintain last failure notification when themes failed to update manually.
if ( ! $result || is_wp_error( $result ) || ! isset( $past_failure_emails[ $theme ] ) ) {
continue;
}
unset( $past_failure_emails[ $theme ] );
}
update_option( 'auto_plugin_theme_update_emails', $past_failure_emails );
return $results; return $results;
} }

View File

@ -941,6 +941,32 @@ class WP_Automatic_Updater {
return; return;
} }
$unique_failures = false;
$past_failure_emails = get_option( 'auto_plugin_theme_update_emails', array() );
// When only failures have occurred, an email should only be sent if there are unique failures.
// A failure is considered unique if an email has not been sent for an update attempt failure
// to a plugin or theme with the same new_version.
if ( 'fail' === $type ) {
foreach ( $failed_updates as $update_type => $failures ) {
foreach ( $failures as $failed_update ) {
if ( ! isset( $past_failure_emails[ $failed_update->item->{$update_type} ] ) ) {
$unique_failures = true;
continue;
}
// Check that the failure represents a new failure based on the new_version.
if ( version_compare( $past_failure_emails[ $failed_update->item->{$update_type} ], $failed_update->item->new_version, '<' ) ) {
$unique_failures = true;
}
}
}
if ( ! $unique_failures ) {
return;
}
}
$body = array(); $body = array();
$successful_plugins = ( ! empty( $successful_updates['plugin'] ) ); $successful_plugins = ( ! empty( $successful_updates['plugin'] ) );
$successful_themes = ( ! empty( $successful_updates['theme'] ) ); $successful_themes = ( ! empty( $successful_updates['theme'] ) );
@ -1017,7 +1043,8 @@ class WP_Automatic_Updater {
$body[] = __( 'These plugins failed to update:' ); $body[] = __( 'These plugins failed to update:' );
foreach ( $failed_updates['plugin'] as $item ) { foreach ( $failed_updates['plugin'] as $item ) {
$body[] = "- {$item->name}"; $body[] = "- {$item->name}";
$past_failure_emails[ $item->item->plugin ] = $item->item->new_version;
} }
$body[] = "\n"; $body[] = "\n";
} }
@ -1027,7 +1054,8 @@ class WP_Automatic_Updater {
$body[] = __( 'These themes failed to update:' ); $body[] = __( 'These themes failed to update:' );
foreach ( $failed_updates['theme'] as $item ) { foreach ( $failed_updates['theme'] as $item ) {
$body[] = "- {$item->name}"; $body[] = "- {$item->name}";
$past_failure_emails[ $item->item->theme ] = $item->item->new_version;
} }
$body[] = "\n"; $body[] = "\n";
} }
@ -1043,6 +1071,7 @@ class WP_Automatic_Updater {
foreach ( $successful_updates['plugin'] as $item ) { foreach ( $successful_updates['plugin'] as $item ) {
$body[] = "- {$item->name}"; $body[] = "- {$item->name}";
unset( $past_failure_emails[ $item->item->plugin ] );
} }
$body[] = "\n"; $body[] = "\n";
} }
@ -1053,6 +1082,7 @@ class WP_Automatic_Updater {
// List successful updates. // List successful updates.
foreach ( $successful_updates['theme'] as $item ) { foreach ( $successful_updates['theme'] as $item ) {
$body[] = "- {$item->name}"; $body[] = "- {$item->name}";
unset( $past_failure_emails[ $item->item->theme ] );
} }
$body[] = "\n"; $body[] = "\n";
} }
@ -1108,7 +1138,11 @@ class WP_Automatic_Updater {
*/ */
$email = apply_filters( 'auto_plugin_theme_update_email', $email, $type, $successful_updates, $failed_updates ); $email = apply_filters( 'auto_plugin_theme_update_email', $email, $type, $successful_updates, $failed_updates );
wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] ); $result = wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
if ( $result ) {
update_option( 'auto_plugin_theme_update_emails', $past_failure_emails );
}
} }
/** /**

View File

@ -534,6 +534,7 @@ function populate_options( array $options = array() ) {
// 5.5.0 // 5.5.0
'blocklist_keys' => '', 'blocklist_keys' => '',
'comment_previously_approved' => 1, 'comment_previously_approved' => 1,
'auto_plugin_theme_update_emails' => array(),
); );
// 3.3.0 // 3.3.0
@ -552,7 +553,13 @@ function populate_options( array $options = array() ) {
$options = wp_parse_args( $options, $defaults ); $options = wp_parse_args( $options, $defaults );
// Set autoload to no for these options. // Set autoload to no for these options.
$fat_options = array( 'moderation_keys', 'recently_edited', 'blocklist_keys', 'uninstall_plugins' ); $fat_options = array(
'moderation_keys',
'recently_edited',
'blocklist_keys',
'uninstall_plugins',
'auto_plugin_theme_update_emails',
);
$keys = "'" . implode( "', '", array_keys( $options ) ) . "'"; $keys = "'" . implode( "', '", array_keys( $options ) ) . "'";
$existing_options = $wpdb->get_col( "SELECT option_name FROM $wpdb->options WHERE option_name in ( $keys )" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $existing_options = $wpdb->get_col( "SELECT option_name FROM $wpdb->options WHERE option_name in ( $keys )" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

View File

@ -20,7 +20,7 @@ $wp_version = '5.5-alpha-47426-src';
* *
* @global int $wp_db_version * @global int $wp_db_version
*/ */
$wp_db_version = 48121; $wp_db_version = 48397;
/** /**
* Holds the TinyMCE version. * Holds the TinyMCE version.