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.
Built from https://develop.svn.wordpress.org/trunk@48397


git-svn-id: http://core.svn.wordpress.org/trunk@48166 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
desrosj 2020-07-07 19:00:02 +00:00
parent 4377e9a44e
commit 48da753ff0
5 changed files with 93 additions and 6 deletions

View File

@ -247,6 +247,15 @@ class Plugin_Upgrader extends WP_Upgrader {
// Force refresh of plugin update information.
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;
}
@ -370,6 +379,20 @@ class Plugin_Upgrader extends WP_Upgrader {
// Cleanup our hooks, in case something else does a upgrade on this connection.
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;
}

View File

@ -352,6 +352,15 @@ class Theme_Upgrader extends WP_Upgrader {
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;
}
@ -479,6 +488,20 @@ class Theme_Upgrader extends WP_Upgrader {
remove_filter( 'upgrader_post_install', array( $this, 'current_after' ) );
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;
}

View File

@ -941,6 +941,32 @@ class WP_Automatic_Updater {
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();
$successful_plugins = ( ! empty( $successful_updates['plugin'] ) );
$successful_themes = ( ! empty( $successful_updates['theme'] ) );
@ -1017,7 +1043,8 @@ class WP_Automatic_Updater {
$body[] = __( 'These plugins failed to update:' );
foreach ( $failed_updates['plugin'] as $item ) {
$body[] = "- {$item->name}";
$body[] = "- {$item->name}";
$past_failure_emails[ $item->item->plugin ] = $item->item->new_version;
}
$body[] = "\n";
}
@ -1027,7 +1054,8 @@ class WP_Automatic_Updater {
$body[] = __( 'These themes failed to update:' );
foreach ( $failed_updates['theme'] as $item ) {
$body[] = "- {$item->name}";
$body[] = "- {$item->name}";
$past_failure_emails[ $item->item->theme ] = $item->item->new_version;
}
$body[] = "\n";
}
@ -1043,6 +1071,7 @@ class WP_Automatic_Updater {
foreach ( $successful_updates['plugin'] as $item ) {
$body[] = "- {$item->name}";
unset( $past_failure_emails[ $item->item->plugin ] );
}
$body[] = "\n";
}
@ -1053,6 +1082,7 @@ class WP_Automatic_Updater {
// List successful updates.
foreach ( $successful_updates['theme'] as $item ) {
$body[] = "- {$item->name}";
unset( $past_failure_emails[ $item->item->theme ] );
}
$body[] = "\n";
}
@ -1108,7 +1138,11 @@ class WP_Automatic_Updater {
*/
$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
'blocklist_keys' => '',
'comment_previously_approved' => 1,
'auto_plugin_theme_update_emails' => array(),
);
// 3.3.0
@ -552,7 +553,13 @@ function populate_options( array $options = array() ) {
$options = wp_parse_args( $options, $defaults );
// 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 ) ) . "'";
$existing_options = $wpdb->get_col( "SELECT option_name FROM $wpdb->options WHERE option_name in ( $keys )" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

View File

@ -13,14 +13,14 @@
*
* @global string $wp_version
*/
$wp_version = '5.5-alpha-48396';
$wp_version = '5.5-alpha-48397';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
*
* @global int $wp_db_version
*/
$wp_db_version = 48121;
$wp_db_version = 48397;
/**
* Holds the TinyMCE version.