2016-05-10 07:45:42 -04:00
< ? php
/**
2016-05-10 07:52:28 -04:00
* Upgrade API : WP_Automatic_Updater class
2016-05-10 07:45:42 -04:00
*
* @ package WordPress
* @ subpackage Upgrader
2016-05-10 07:52:28 -04:00
* @ since 4.6 . 0
2016-05-10 07:45:42 -04:00
*/
/**
* Core class used for handling automatic background updates .
*
* @ since 3.7 . 0
2016-05-13 16:59:27 -04:00
* @ since 4.6 . 0 Moved to its own file from wp - admin / includes / class - wp - upgrader . php .
2016-05-10 07:45:42 -04:00
*/
class WP_Automatic_Updater {
/**
* Tracks update results during processing .
*
* @ var array
*/
protected $update_results = array ();
/**
* Whether the entire automatic updater is disabled .
*
* @ since 3.7 . 0
*/
public function is_disabled () {
// Background updates are disabled if you don't want file changes.
2017-11-30 18:11:00 -05:00
if ( ! wp_is_file_mod_allowed ( 'automatic_updater' ) ) {
2016-05-10 07:45:42 -04:00
return true ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
2017-11-30 18:11:00 -05:00
if ( wp_installing () ) {
2016-05-10 07:45:42 -04:00
return true ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
// More fine grained control can be done through the WP_AUTO_UPDATE_CORE constant and filters.
$disabled = defined ( 'AUTOMATIC_UPDATER_DISABLED' ) && AUTOMATIC_UPDATER_DISABLED ;
/**
2016-05-22 14:01:30 -04:00
* Filters whether to entirely disable background updates .
2016-05-10 07:45:42 -04:00
*
* There are more fine - grained filters and controls for selective disabling .
* This filter parallels the AUTOMATIC_UPDATER_DISABLED constant in name .
*
* This also disables update notification emails . That may change in the future .
*
* @ since 3.7 . 0
*
* @ param bool $disabled Whether the updater should be disabled .
*/
return apply_filters ( 'automatic_updater_disabled' , $disabled );
}
/**
* Check for version control checkouts .
*
* Checks for Subversion , Git , Mercurial , and Bazaar . It recursively looks up the
* filesystem to the top of the drive , erring on the side of detecting a VCS
* checkout somewhere .
*
* ABSPATH is always checked in addition to whatever $context is ( which may be the
* wp - content directory , for example ) . The underlying assumption is that if you are
* using version control * anywhere * , then you should be making decisions for
* how things get updated .
*
* @ since 3.7 . 0
*
* @ param string $context The filesystem path to check , in addition to ABSPATH .
*/
public function is_vcs_checkout ( $context ) {
$context_dirs = array ( untrailingslashit ( $context ) );
2017-11-30 18:11:00 -05:00
if ( $context !== ABSPATH ) {
2016-05-10 07:45:42 -04:00
$context_dirs [] = untrailingslashit ( ABSPATH );
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
2017-11-30 18:11:00 -05:00
$vcs_dirs = array ( '.svn' , '.git' , '.hg' , '.bzr' );
2016-05-10 07:45:42 -04:00
$check_dirs = array ();
foreach ( $context_dirs as $context_dir ) {
// Walk up from $context_dir to the root.
do {
$check_dirs [] = $context_dir ;
// Once we've hit '/' or 'C:\', we need to stop. dirname will keep returning the input here.
2017-11-30 18:11:00 -05:00
if ( $context_dir == dirname ( $context_dir ) ) {
2016-05-10 07:45:42 -04:00
break ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
2017-11-30 18:11:00 -05:00
// Continue one level at a time.
2016-05-10 07:45:42 -04:00
} while ( $context_dir = dirname ( $context_dir ) );
}
$check_dirs = array_unique ( $check_dirs );
// Search all directories we've found for evidence of version control.
foreach ( $vcs_dirs as $vcs_dir ) {
foreach ( $check_dirs as $check_dir ) {
2017-11-30 18:11:00 -05:00
if ( $checkout = @ is_dir ( rtrim ( $check_dir , '\\/' ) . " / $vcs_dir " ) ) {
2016-05-10 07:45:42 -04:00
break 2 ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
}
}
/**
2016-05-22 14:01:30 -04:00
* Filters whether the automatic updater should consider a filesystem
2016-05-10 07:45:42 -04:00
* location to be potentially managed by a version control system .
*
* @ since 3.7 . 0
*
* @ param bool $checkout Whether a VCS checkout was discovered at $context
* or ABSPATH , or anywhere higher .
* @ param string $context The filesystem context ( a path ) against which
* filesystem status should be checked .
*/
return apply_filters ( 'automatic_updates_is_vcs_checkout' , $checkout , $context );
}
/**
* Tests to see if we can and should update a specific item .
*
* @ since 3.7 . 0
*
* @ global wpdb $wpdb WordPress database abstraction object .
*
* @ param string $type The type of update being checked : 'core' , 'theme' ,
* 'plugin' , 'translation' .
* @ param object $item The update offer .
* @ param string $context The filesystem context ( a path ) against which filesystem
* access and status should be checked .
*/
public function should_update ( $type , $item , $context ) {
// Used to see if WP_Filesystem is set up to allow unattended updates.
$skin = new Automatic_Upgrader_Skin ;
2017-11-30 18:11:00 -05:00
if ( $this -> is_disabled () ) {
2016-05-10 07:45:42 -04:00
return false ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
// Only relax the filesystem checks when the update doesn't include new files
$allow_relaxed_file_ownership = false ;
if ( 'core' == $type && isset ( $item -> new_files ) && ! $item -> new_files ) {
$allow_relaxed_file_ownership = true ;
}
// If we can't do an auto core update, we may still be able to email the user.
if ( ! $skin -> request_filesystem_credentials ( false , $context , $allow_relaxed_file_ownership ) || $this -> is_vcs_checkout ( $context ) ) {
2017-11-30 18:11:00 -05:00
if ( 'core' == $type ) {
2016-05-10 07:45:42 -04:00
$this -> send_core_update_notification_email ( $item );
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
return false ;
}
// Next up, is this an item we can update?
2017-11-30 18:11:00 -05:00
if ( 'core' == $type ) {
2016-05-10 07:45:42 -04:00
$update = Core_Upgrader :: should_update_to_version ( $item -> current );
2017-11-30 18:11:00 -05:00
} else {
2016-05-10 07:45:42 -04:00
$update = ! empty ( $item -> autoupdate );
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
/**
2016-05-22 14:01:30 -04:00
* Filters whether to automatically update core , a plugin , a theme , or a language .
2016-05-10 07:45:42 -04:00
*
* The dynamic portion of the hook name , `$type` , refers to the type of update
* being checked . Can be 'core' , 'theme' , 'plugin' , or 'translation' .
*
* Generally speaking , plugins , themes , and major core versions are not updated
* by default , while translations and minor and development versions for core
* are updated by default .
*
2018-03-11 06:30:33 -04:00
* See the { @ see 'allow_dev_auto_core_updates' }, { @ see 'allow_minor_auto_core_updates' },
2016-05-10 07:45:42 -04:00
* and { @ see 'allow_major_auto_core_updates' } filters for a more straightforward way to
* adjust core updates .
*
* @ since 3.7 . 0
*
* @ param bool $update Whether to update .
* @ param object $item The update offer .
*/
2016-08-22 14:25:31 -04:00
$update = apply_filters ( " auto_update_ { $type } " , $update , $item );
2016-05-10 07:45:42 -04:00
if ( ! $update ) {
2017-11-30 18:11:00 -05:00
if ( 'core' == $type ) {
2016-05-10 07:45:42 -04:00
$this -> send_core_update_notification_email ( $item );
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
return false ;
}
// If it's a core update, are we actually compatible with its requirements?
if ( 'core' == $type ) {
global $wpdb ;
$php_compat = version_compare ( phpversion (), $item -> php_version , '>=' );
2017-11-30 18:11:00 -05:00
if ( file_exists ( WP_CONTENT_DIR . '/db.php' ) && empty ( $wpdb -> is_mysql ) ) {
2016-05-10 07:45:42 -04:00
$mysql_compat = true ;
2017-11-30 18:11:00 -05:00
} else {
2016-05-10 07:45:42 -04:00
$mysql_compat = version_compare ( $wpdb -> db_version (), $item -> mysql_version , '>=' );
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
2017-11-30 18:11:00 -05:00
if ( ! $php_compat || ! $mysql_compat ) {
2016-05-10 07:45:42 -04:00
return false ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
}
2019-04-12 10:02:53 -04:00
// If updating a plugin, ensure the minimum PHP version requirements are satisfied.
if ( 'plugin' === $type ) {
if ( ! empty ( $item -> requires_php ) && version_compare ( phpversion (), $item -> requires_php , '<' ) ) {
return false ;
}
}
2016-05-10 07:45:42 -04:00
return true ;
}
/**
* Notifies an administrator of a core update .
*
* @ since 3.7 . 0
*
* @ param object $item The update offer .
*/
protected function send_core_update_notification_email ( $item ) {
$notified = get_site_option ( 'auto_core_update_notified' );
// Don't notify if we've already notified the same email address of the same version.
2017-11-30 18:11:00 -05:00
if ( $notified && $notified [ 'email' ] == get_site_option ( 'admin_email' ) && $notified [ 'version' ] == $item -> current ) {
2016-05-10 07:45:42 -04:00
return false ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
// See if we need to notify users of a core update.
$notify = ! empty ( $item -> notify_email );
/**
2016-05-22 14:01:30 -04:00
* Filters whether to notify the site administrator of a new core update .
2016-05-10 07:45:42 -04:00
*
* By default , administrators are notified when the update offer received
* from WordPress . org sets a particular flag . This allows some discretion
* in if and when to notify .
*
* This filter is only evaluated once per release . If the same email address
* was already notified of the same new version , WordPress won ' t repeatedly
* email the administrator .
*
* This filter is also used on about . php to check if a plugin has disabled
* these notifications .
*
* @ since 3.7 . 0
*
* @ param bool $notify Whether the site administrator is notified .
* @ param object $item The update offer .
*/
2017-11-30 18:11:00 -05:00
if ( ! apply_filters ( 'send_core_update_notification_email' , $notify , $item ) ) {
2016-05-10 07:45:42 -04:00
return false ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
$this -> send_email ( 'manual' , $item );
return true ;
}
/**
* Update an item , if appropriate .
*
* @ since 3.7 . 0
*
* @ param string $type The type of update being checked : 'core' , 'theme' , 'plugin' , 'translation' .
* @ param object $item The update offer .
*
* @ return null | WP_Error
*/
public function update ( $type , $item ) {
$skin = new Automatic_Upgrader_Skin ;
switch ( $type ) {
case 'core' :
// The Core upgrader doesn't use the Upgrader's skin during the actual main part of the upgrade, instead, firing a filter.
add_filter ( 'update_feedback' , array ( $skin , 'feedback' ) );
$upgrader = new Core_Upgrader ( $skin );
$context = ABSPATH ;
break ;
case 'plugin' :
$upgrader = new Plugin_Upgrader ( $skin );
$context = WP_PLUGIN_DIR ; // We don't support custom Plugin directories, or updates for WPMU_PLUGIN_DIR
break ;
case 'theme' :
$upgrader = new Theme_Upgrader ( $skin );
$context = get_theme_root ( $item -> theme );
break ;
case 'translation' :
$upgrader = new Language_Pack_Upgrader ( $skin );
$context = WP_CONTENT_DIR ; // WP_LANG_DIR;
break ;
}
// Determine whether we can and should perform this update.
2017-11-30 18:11:00 -05:00
if ( ! $this -> should_update ( $type , $item , $context ) ) {
2016-05-10 07:45:42 -04:00
return false ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
/**
* Fires immediately prior to an auto - update .
*
* @ since 4.4 . 0
*
* @ param string $type The type of update being checked : 'core' , 'theme' , 'plugin' , or 'translation' .
* @ param object $item The update offer .
* @ param string $context The filesystem context ( a path ) against which filesystem access and status
* should be checked .
*/
do_action ( 'pre_auto_update' , $type , $item , $context );
$upgrader_item = $item ;
switch ( $type ) {
case 'core' :
2018-08-30 09:36:25 -04:00
/* translators: %s: WordPress version */
2016-05-10 07:45:42 -04:00
$skin -> feedback ( __ ( 'Updating to WordPress %s' ), $item -> version );
2018-08-30 09:36:25 -04:00
/* translators: %s: WordPress version */
2016-05-10 07:45:42 -04:00
$item_name = sprintf ( __ ( 'WordPress %s' ), $item -> version );
break ;
case 'theme' :
$upgrader_item = $item -> theme ;
2017-11-30 18:11:00 -05:00
$theme = wp_get_theme ( $upgrader_item );
$item_name = $theme -> Get ( 'Name' );
2018-08-30 09:36:25 -04:00
/* translators: %s: Theme name */
2016-05-10 07:45:42 -04:00
$skin -> feedback ( __ ( 'Updating theme: %s' ), $item_name );
break ;
case 'plugin' :
$upgrader_item = $item -> plugin ;
2017-11-30 18:11:00 -05:00
$plugin_data = get_plugin_data ( $context . '/' . $upgrader_item );
$item_name = $plugin_data [ 'Name' ];
2018-08-30 09:36:25 -04:00
/* translators: %s: Plugin name */
2016-05-10 07:45:42 -04:00
$skin -> feedback ( __ ( 'Updating plugin: %s' ), $item_name );
break ;
case 'translation' :
$language_item_name = $upgrader -> get_name_for_update ( $item );
2018-08-30 09:36:25 -04:00
/* translators: %s: Name of language item */
$item_name = sprintf ( __ ( 'Translations for %s' ), $language_item_name );
/* translators: 1: Name of language item, 2: Language */
2016-05-10 07:45:42 -04:00
$skin -> feedback ( sprintf ( __ ( 'Updating translations for %1$s (%2$s)…' ), $language_item_name , $item -> language ) );
break ;
}
$allow_relaxed_file_ownership = false ;
if ( 'core' == $type && isset ( $item -> new_files ) && ! $item -> new_files ) {
$allow_relaxed_file_ownership = true ;
}
// Boom, This sites about to get a whole new splash of paint!
2017-11-30 18:11:00 -05:00
$upgrade_result = $upgrader -> upgrade (
2018-08-16 21:51:36 -04:00
$upgrader_item ,
array (
2017-11-30 18:11:00 -05:00
'clear_update_cache' => false ,
// Always use partial builds if possible for core updates.
'pre_check_md5' => false ,
// Only available for core updates.
'attempt_rollback' => true ,
// Allow relaxed file ownership in some scenarios
'allow_relaxed_file_ownership' => $allow_relaxed_file_ownership ,
)
);
2016-05-10 07:45:42 -04:00
// If the filesystem is unavailable, false is returned.
if ( false === $upgrade_result ) {
$upgrade_result = new WP_Error ( 'fs_unavailable' , __ ( 'Could not access filesystem.' ) );
}
if ( 'core' == $type ) {
if ( is_wp_error ( $upgrade_result ) && ( 'up_to_date' == $upgrade_result -> get_error_code () || 'locked' == $upgrade_result -> get_error_code () ) ) {
// These aren't actual errors, treat it as a skipped-update instead to avoid triggering the post-core update failure routines.
return false ;
}
// Core doesn't output this, so let's append it so we don't get confused.
if ( is_wp_error ( $upgrade_result ) ) {
$skin -> error ( __ ( 'Installation Failed' ), $upgrade_result );
} else {
$skin -> feedback ( __ ( 'WordPress updated successfully' ) );
}
}
$this -> update_results [ $type ][] = ( object ) array (
'item' => $item ,
'result' => $upgrade_result ,
'name' => $item_name ,
2017-11-30 18:11:00 -05:00
'messages' => $skin -> get_upgrade_messages (),
2016-05-10 07:45:42 -04:00
);
return $upgrade_result ;
}
/**
* Kicks off the background update process , looping through all pending updates .
*
* @ since 3.7 . 0
*/
public function run () {
2017-11-30 18:11:00 -05:00
if ( $this -> is_disabled () ) {
2016-05-10 07:45:42 -04:00
return ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
2017-11-30 18:11:00 -05:00
if ( ! is_main_network () || ! is_main_site () ) {
2016-05-10 07:45:42 -04:00
return ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
2017-11-30 18:11:00 -05:00
if ( ! WP_Upgrader :: create_lock ( 'auto_updater' ) ) {
2016-05-10 07:45:42 -04:00
return ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
// Don't automatically run these thins, as we'll handle it ourselves
remove_action ( 'upgrader_process_complete' , array ( 'Language_Pack_Upgrader' , 'async_upgrade' ), 20 );
remove_action ( 'upgrader_process_complete' , 'wp_version_check' );
remove_action ( 'upgrader_process_complete' , 'wp_update_plugins' );
remove_action ( 'upgrader_process_complete' , 'wp_update_themes' );
// Next, Plugins
wp_update_plugins (); // Check for Plugin updates
$plugin_updates = get_site_transient ( 'update_plugins' );
2017-11-30 18:11:00 -05:00
if ( $plugin_updates && ! empty ( $plugin_updates -> response ) ) {
2016-05-10 07:45:42 -04:00
foreach ( $plugin_updates -> response as $plugin ) {
$this -> update ( 'plugin' , $plugin );
}
// Force refresh of plugin update information
wp_clean_plugins_cache ();
}
// Next, those themes we all love
wp_update_themes (); // Check for Theme updates
$theme_updates = get_site_transient ( 'update_themes' );
2017-11-30 18:11:00 -05:00
if ( $theme_updates && ! empty ( $theme_updates -> response ) ) {
2016-05-10 07:45:42 -04:00
foreach ( $theme_updates -> response as $theme ) {
$this -> update ( 'theme' , ( object ) $theme );
}
// Force refresh of theme update information
wp_clean_themes_cache ();
}
// Next, Process any core update
wp_version_check (); // Check for Core updates
$core_update = find_core_auto_update ();
2017-11-30 18:11:00 -05:00
if ( $core_update ) {
2016-05-10 07:45:42 -04:00
$this -> update ( 'core' , $core_update );
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
// Clean up, and check for any pending translations
// (Core_Upgrader checks for core updates)
$theme_stats = array ();
if ( isset ( $this -> update_results [ 'theme' ] ) ) {
foreach ( $this -> update_results [ 'theme' ] as $upgrade ) {
$theme_stats [ $upgrade -> item -> theme ] = ( true === $upgrade -> result );
}
}
wp_update_themes ( $theme_stats ); // Check for Theme updates
$plugin_stats = array ();
if ( isset ( $this -> update_results [ 'plugin' ] ) ) {
foreach ( $this -> update_results [ 'plugin' ] as $upgrade ) {
$plugin_stats [ $upgrade -> item -> plugin ] = ( true === $upgrade -> result );
}
}
wp_update_plugins ( $plugin_stats ); // Check for Plugin updates
// Finally, Process any new translations
$language_updates = wp_get_translation_updates ();
if ( $language_updates ) {
foreach ( $language_updates as $update ) {
$this -> update ( 'translation' , $update );
}
// Clear existing caches
wp_clean_update_cache ();
wp_version_check (); // check for Core updates
wp_update_themes (); // Check for Theme updates
wp_update_plugins (); // Check for Plugin updates
}
2017-08-22 07:52:48 -04:00
// Send debugging email to admin for all development installations.
2016-05-10 07:45:42 -04:00
if ( ! empty ( $this -> update_results ) ) {
2016-08-31 01:49:37 -04:00
$development_version = false !== strpos ( get_bloginfo ( 'version' ), '-' );
2016-05-10 07:45:42 -04:00
/**
2016-05-22 14:01:30 -04:00
* Filters whether to send a debugging email for each automatic background update .
2016-05-10 07:45:42 -04:00
*
* @ since 3.7 . 0
*
* @ param bool $development_version By default , emails are sent if the
* install is a development version .
* Return false to avoid the email .
*/
2017-11-30 18:11:00 -05:00
if ( apply_filters ( 'automatic_updates_send_debug_email' , $development_version ) ) {
2016-05-10 07:45:42 -04:00
$this -> send_debug_email ();
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
2017-11-30 18:11:00 -05:00
if ( ! empty ( $this -> update_results [ 'core' ] ) ) {
2016-05-10 07:45:42 -04:00
$this -> after_core_update ( $this -> update_results [ 'core' ][ 0 ] );
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
/**
* Fires after all automatic updates have run .
*
* @ since 3.8 . 0
*
* @ param array $update_results The results of all attempted updates .
*/
do_action ( 'automatic_updates_complete' , $this -> update_results );
}
WP_Upgrader :: release_lock ( 'auto_updater' );
}
/**
* If we tried to perform a core update , check if we should send an email ,
* and if we need to avoid processing future updates .
*
2017-10-18 13:48:49 -04:00
* @ since 3.7 . 0
2016-05-10 07:45:42 -04:00
*
* @ param object $update_result The result of the core update . Includes the update offer and result .
*/
protected function after_core_update ( $update_result ) {
2016-08-31 01:49:37 -04:00
$wp_version = get_bloginfo ( 'version' );
2016-05-10 07:45:42 -04:00
$core_update = $update_result -> item ;
$result = $update_result -> result ;
if ( ! is_wp_error ( $result ) ) {
$this -> send_email ( 'success' , $core_update );
return ;
}
$error_code = $result -> get_error_code ();
// Any of these WP_Error codes are critical failures, as in they occurred after we started to copy core files.
// We should not try to perform a background update again until there is a successful one-click update performed by the user.
$critical = false ;
if ( $error_code === 'disk_full' || false !== strpos ( $error_code , '__copy_dir' ) ) {
$critical = true ;
} elseif ( $error_code === 'rollback_was_required' && is_wp_error ( $result -> get_error_data () -> rollback ) ) {
// A rollback is only critical if it failed too.
2017-11-30 18:11:00 -05:00
$critical = true ;
2016-05-10 07:45:42 -04:00
$rollback_result = $result -> get_error_data () -> rollback ;
} elseif ( false !== strpos ( $error_code , 'do_rollback' ) ) {
$critical = true ;
}
if ( $critical ) {
$critical_data = array (
'attempted' => $core_update -> current ,
'current' => $wp_version ,
'error_code' => $error_code ,
'error_data' => $result -> get_error_data (),
'timestamp' => time (),
'critical' => true ,
);
if ( isset ( $rollback_result ) ) {
$critical_data [ 'rollback_code' ] = $rollback_result -> get_error_code ();
$critical_data [ 'rollback_data' ] = $rollback_result -> get_error_data ();
}
update_site_option ( 'auto_core_update_failed' , $critical_data );
$this -> send_email ( 'critical' , $core_update , $result );
return ;
}
/*
* Any other WP_Error code ( like download_failed or files_not_writable ) occurs before
* we tried to copy over core files . Thus , the failures are early and graceful .
*
* We should avoid trying to perform a background update again for the same version .
* But we can try again if another version is released .
*
* For certain 'transient' failures , like download_failed , we should allow retries .
* In fact , let 's schedule a special update for an hour from now. (It' s possible
* the issue could actually be on WordPress . org ' s side . ) If that one fails , then email .
*/
2017-11-30 18:11:00 -05:00
$send = true ;
$transient_failures = array ( 'incompatible_archive' , 'download_failed' , 'insane_distro' , 'locked' );
if ( in_array ( $error_code , $transient_failures ) && ! get_site_option ( 'auto_core_update_failed' ) ) {
wp_schedule_single_event ( time () + HOUR_IN_SECONDS , 'wp_maybe_auto_update' );
$send = false ;
}
$n = get_site_option ( 'auto_core_update_notified' );
2016-05-10 07:45:42 -04:00
// Don't notify if we've already notified the same email address of the same version of the same notification type.
2017-11-30 18:11:00 -05:00
if ( $n && 'fail' == $n [ 'type' ] && $n [ 'email' ] == get_site_option ( 'admin_email' ) && $n [ 'version' ] == $core_update -> current ) {
2016-05-10 07:45:42 -04:00
$send = false ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
2017-11-30 18:11:00 -05:00
update_site_option (
2018-08-16 21:51:36 -04:00
'auto_core_update_failed' ,
array (
2017-11-30 18:11:00 -05:00
'attempted' => $core_update -> current ,
'current' => $wp_version ,
'error_code' => $error_code ,
'error_data' => $result -> get_error_data (),
'timestamp' => time (),
'retry' => in_array ( $error_code , $transient_failures ),
)
);
2016-05-10 07:45:42 -04:00
2017-11-30 18:11:00 -05:00
if ( $send ) {
2016-05-10 07:45:42 -04:00
$this -> send_email ( 'fail' , $core_update , $result );
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
}
/**
* Sends an email upon the completion or failure of a background core update .
*
* @ since 3.7 . 0
*
* @ param string $type The type of email to send . Can be one of 'success' , 'fail' , 'manual' , 'critical' .
* @ param object $core_update The update offer that was attempted .
* @ param mixed $result Optional . The result for the core update . Can be WP_Error .
*/
protected function send_email ( $type , $core_update , $result = null ) {
2017-11-30 18:11:00 -05:00
update_site_option (
2018-08-16 21:51:36 -04:00
'auto_core_update_notified' ,
array (
2017-11-30 18:11:00 -05:00
'type' => $type ,
'email' => get_site_option ( 'admin_email' ),
'version' => $core_update -> current ,
'timestamp' => time (),
)
);
2016-05-10 07:45:42 -04:00
$next_user_core_update = get_preferred_from_update_core ();
// If the update transient is empty, use the update we just performed
2017-11-30 18:11:00 -05:00
if ( ! $next_user_core_update ) {
2016-05-10 07:45:42 -04:00
$next_user_core_update = $core_update ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
$newer_version_available = ( 'upgrade' == $next_user_core_update -> response && version_compare ( $next_user_core_update -> version , $core_update -> version , '>' ) );
/**
2016-05-22 14:01:30 -04:00
* Filters whether to send an email following an automatic background core update .
2016-05-10 07:45:42 -04:00
*
* @ since 3.7 . 0
*
* @ param bool $send Whether to send the email . Default true .
* @ param string $type The type of email to send . Can be one of
* 'success' , 'fail' , 'critical' .
* @ param object $core_update The update offer that was attempted .
* @ param mixed $result The result for the core update . Can be WP_Error .
*/
2017-11-30 18:11:00 -05:00
if ( 'manual' !== $type && ! apply_filters ( 'auto_core_update_send_email' , true , $type , $core_update , $result ) ) {
2016-05-10 07:45:42 -04:00
return ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
switch ( $type ) {
2017-11-30 18:11:00 -05:00
case 'success' : // We updated.
2019-04-15 18:39:50 -04:00
/* translators: Site updated notification email subject. 1: Site title, 2: WordPress version number. */
2016-05-10 07:45:42 -04:00
$subject = __ ( '[%1$s] Your site has updated to WordPress %2$s' );
break ;
2017-11-30 18:11:00 -05:00
case 'fail' : // We tried to update but couldn't.
case 'manual' : // We can't update (and made no attempt).
2019-04-15 19:56:52 -04:00
/* translators: Update available notification email subject. 1: Site title, 2: WordPress version number. */
2016-05-10 07:45:42 -04:00
$subject = __ ( '[%1$s] WordPress %2$s is available. Please update!' );
break ;
2017-11-30 18:11:00 -05:00
case 'critical' : // We tried to update, started to copy files, then things went wrong.
2019-04-15 18:39:50 -04:00
/* translators: Site down notification email subject. 1: Site title. */
2016-05-10 07:45:42 -04:00
$subject = __ ( '[%1$s] URGENT: Your site may be down due to a failed update' );
break ;
2017-11-30 18:11:00 -05:00
default :
2016-05-10 07:45:42 -04:00
return ;
}
// If the auto update is not to the latest version, say that the current version of WP is available instead.
$version = 'success' === $type ? $core_update -> current : $next_user_core_update -> current ;
$subject = sprintf ( $subject , wp_specialchars_decode ( get_option ( 'blogname' ), ENT_QUOTES ), $version );
$body = '' ;
switch ( $type ) {
2017-11-30 18:11:00 -05:00
case 'success' :
2018-08-30 09:36:25 -04:00
/* translators: 1: Home URL, 2: WordPress version */
2016-05-10 07:45:42 -04:00
$body .= sprintf ( __ ( 'Howdy! Your site at %1$s has been updated automatically to WordPress %2$s.' ), home_url (), $core_update -> current );
$body .= " \n \n " ;
2017-11-30 18:11:00 -05:00
if ( ! $newer_version_available ) {
2016-05-10 07:45:42 -04:00
$body .= __ ( 'No further action is needed on your part.' ) . ' ' ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
// Can only reference the About screen if their update was successful.
list ( $about_version ) = explode ( '-' , $core_update -> current , 2 );
2018-08-30 09:36:25 -04:00
/* translators: %s: WordPress core version */
$body .= sprintf ( __ ( 'For more on version %s, see the About WordPress screen:' ), $about_version );
$body .= " \n " . admin_url ( 'about.php' );
2016-05-10 07:45:42 -04:00
if ( $newer_version_available ) {
2018-08-30 09:36:25 -04:00
/* translators: %s: WordPress core latest version */
2016-05-10 07:45:42 -04:00
$body .= " \n \n " . sprintf ( __ ( 'WordPress %s is also now available.' ), $next_user_core_update -> current ) . ' ' ;
$body .= __ ( 'Updating is easy and only takes a few moments:' );
$body .= " \n " . network_admin_url ( 'update-core.php' );
}
break ;
2017-11-30 18:11:00 -05:00
case 'fail' :
case 'manual' :
2018-08-30 09:36:25 -04:00
/* translators: 1: Home URL, 2: WordPress core latest version */
2016-05-10 07:45:42 -04:00
$body .= sprintf ( __ ( 'Please update your site at %1$s to WordPress %2$s.' ), home_url (), $next_user_core_update -> current );
$body .= " \n \n " ;
// Don't show this message if there is a newer version available.
// Potential for confusion, and also not useful for them to know at this point.
2017-11-30 18:11:00 -05:00
if ( 'fail' == $type && ! $newer_version_available ) {
2016-05-10 07:45:42 -04:00
$body .= __ ( 'We tried but were unable to update your site automatically.' ) . ' ' ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
$body .= __ ( 'Updating is easy and only takes a few moments:' );
$body .= " \n " . network_admin_url ( 'update-core.php' );
break ;
2017-11-30 18:11:00 -05:00
case 'critical' :
if ( $newer_version_available ) {
2018-08-30 09:36:25 -04:00
/* translators: 1: Home URL, 2: WordPress core latest version */
2016-05-10 07:45:42 -04:00
$body .= sprintf ( __ ( 'Your site at %1$s experienced a critical failure while trying to update WordPress to version %2$s.' ), home_url (), $core_update -> current );
2017-11-30 18:11:00 -05:00
} else {
2018-08-30 09:36:25 -04:00
/* translators: 1: Home URL, 2: Core update version */
2016-05-10 07:45:42 -04:00
$body .= sprintf ( __ ( 'Your site at %1$s experienced a critical failure while trying to update to the latest version of WordPress, %2$s.' ), home_url (), $core_update -> current );
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
$body .= " \n \n " . __ ( " This means your site may be offline or broken. Don't panic; this can be fixed. " );
$body .= " \n \n " . __ ( " Please check out your site now. It's possible that everything is working. If it says you need to update, you should do so: " );
$body .= " \n " . network_admin_url ( 'update-core.php' );
break ;
}
$critical_support = 'critical' === $type && ! empty ( $core_update -> support_email );
if ( $critical_support ) {
// Support offer if available.
2018-08-30 09:36:25 -04:00
/* translators: %s: Support e-mail */
2017-11-30 18:11:00 -05:00
$body .= " \n \n " . sprintf ( __ ( 'The WordPress team is willing to help you. Forward this email to %s and the team will work with you to make sure your site is working.' ), $core_update -> support_email );
2016-05-10 07:45:42 -04:00
} else {
// Add a note about the support forums.
$body .= " \n \n " . __ ( 'If you experience any issues or need support, the volunteers in the WordPress.org support forums may be able to help.' );
$body .= " \n " . __ ( 'https://wordpress.org/support/' );
}
// Updates are important!
if ( $type != 'success' || $newer_version_available ) {
$body .= " \n \n " . __ ( 'Keeping your site updated is important for security. It also makes the internet a safer place for you and your readers.' );
}
if ( $critical_support ) {
2017-11-30 18:11:00 -05:00
$body .= ' ' . __ ( " If you reach out to us, we'll also ensure you'll never have this problem again. " );
2016-05-10 07:45:42 -04:00
}
// If things are successful and we're now on the latest, mention plugins and themes if any are out of date.
if ( $type == 'success' && ! $newer_version_available && ( get_plugin_updates () || get_theme_updates () ) ) {
$body .= " \n \n " . __ ( 'You also have some plugins or themes with updates available. Update them now:' );
$body .= " \n " . network_admin_url ();
}
$body .= " \n \n " . __ ( 'The WordPress Team' ) . " \n " ;
if ( 'critical' == $type && is_wp_error ( $result ) ) {
$body .= " \n *** \n \n " ;
2018-08-30 09:36:25 -04:00
/* translators: %s: WordPress version */
2016-08-31 01:49:37 -04:00
$body .= sprintf ( __ ( 'Your site was running version %s.' ), get_bloginfo ( 'version' ) );
2016-05-10 07:45:42 -04:00
$body .= ' ' . __ ( 'We have some data that describes the error your site encountered.' );
$body .= ' ' . __ ( 'Your hosting company, support forum volunteers, or a friendly developer may be able to use this information to help you:' );
// If we had a rollback and we're still critical, then the rollback failed too.
// Loop through all errors (the main WP_Error, the update result, the rollback result) for code, data, etc.
2017-11-30 18:11:00 -05:00
if ( 'rollback_was_required' == $result -> get_error_code () ) {
2016-05-10 07:45:42 -04:00
$errors = array ( $result , $result -> get_error_data () -> update , $result -> get_error_data () -> rollback );
2017-11-30 18:11:00 -05:00
} else {
2016-05-10 07:45:42 -04:00
$errors = array ( $result );
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
foreach ( $errors as $error ) {
2017-11-30 18:11:00 -05:00
if ( ! is_wp_error ( $error ) ) {
2016-05-10 07:45:42 -04:00
continue ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
$error_code = $error -> get_error_code ();
2018-08-30 09:36:25 -04:00
/* translators: %s: Error code */
$body .= " \n \n " . sprintf ( __ ( 'Error code: %s' ), $error_code );
2017-11-30 18:11:00 -05:00
if ( 'rollback_was_required' == $error_code ) {
2016-05-10 07:45:42 -04:00
continue ;
2017-11-30 18:11:00 -05:00
}
if ( $error -> get_error_message () ) {
2016-05-10 07:45:42 -04:00
$body .= " \n " . $error -> get_error_message ();
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
$error_data = $error -> get_error_data ();
2017-11-30 18:11:00 -05:00
if ( $error_data ) {
2016-05-10 07:45:42 -04:00
$body .= " \n " . implode ( ', ' , ( array ) $error_data );
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
}
$body .= " \n " ;
}
2017-11-30 18:11:00 -05:00
$to = get_site_option ( 'admin_email' );
2016-05-10 07:45:42 -04:00
$headers = '' ;
$email = compact ( 'to' , 'subject' , 'body' , 'headers' );
/**
2016-05-22 14:01:30 -04:00
* Filters the email sent following an automatic background core update .
2016-05-10 07:45:42 -04:00
*
* @ since 3.7 . 0
*
* @ param array $email {
* Array of email arguments that will be passed to wp_mail () .
*
* @ type string $to The email recipient . An array of emails
* can be returned , as handled by wp_mail () .
* @ type string $subject The email ' s subject .
* @ type string $body The email message body .
* @ type string $headers Any email headers , defaults to no headers .
* }
* @ param string $type The type of email being sent . Can be one of
* 'success' , 'fail' , 'manual' , 'critical' .
* @ param object $core_update The update offer that was attempted .
* @ param mixed $result The result for the core update . Can be WP_Error .
*/
$email = apply_filters ( 'auto_core_update_email' , $email , $type , $core_update , $result );
wp_mail ( $email [ 'to' ], wp_specialchars_decode ( $email [ 'subject' ] ), $email [ 'body' ], $email [ 'headers' ] );
}
/**
* Prepares and sends an email of a full log of background update results , useful for debugging and geekery .
*
* @ since 3.7 . 0
*/
protected function send_debug_email () {
$update_count = 0 ;
2017-11-30 18:11:00 -05:00
foreach ( $this -> update_results as $type => $updates ) {
2016-05-10 07:45:42 -04:00
$update_count += count ( $updates );
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
2017-11-30 18:11:00 -05:00
$body = array ();
2016-05-10 07:45:42 -04:00
$failures = 0 ;
2018-08-30 09:36:25 -04:00
/* translators: %s: Network home URL */
2016-05-10 07:45:42 -04:00
$body [] = sprintf ( __ ( 'WordPress site: %s' ), network_home_url ( '/' ) );
// Core
if ( isset ( $this -> update_results [ 'core' ] ) ) {
$result = $this -> update_results [ 'core' ][ 0 ];
if ( $result -> result && ! is_wp_error ( $result -> result ) ) {
2018-08-30 09:36:25 -04:00
/* translators: %s: WordPress core version */
2016-05-10 07:45:42 -04:00
$body [] = sprintf ( __ ( 'SUCCESS: WordPress was successfully updated to %s' ), $result -> name );
} else {
2018-08-30 09:36:25 -04:00
/* translators: %s: WordPress core version */
2016-05-10 07:45:42 -04:00
$body [] = sprintf ( __ ( 'FAILED: WordPress failed to update to %s' ), $result -> name );
$failures ++ ;
}
$body [] = '' ;
}
// Plugins, Themes, Translations
foreach ( array ( 'plugin' , 'theme' , 'translation' ) as $type ) {
2017-11-30 18:11:00 -05:00
if ( ! isset ( $this -> update_results [ $type ] ) ) {
2016-05-10 07:45:42 -04:00
continue ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
$success_items = wp_list_filter ( $this -> update_results [ $type ], array ( 'result' => true ) );
if ( $success_items ) {
$messages = array (
'plugin' => __ ( 'The following plugins were successfully updated:' ),
'theme' => __ ( 'The following themes were successfully updated:' ),
'translation' => __ ( 'The following translations were successfully updated:' ),
);
$body [] = $messages [ $type ];
foreach ( wp_list_pluck ( $success_items , 'name' ) as $name ) {
2018-08-30 09:36:25 -04:00
/* translators: %s: name of plugin / theme / translations */
2016-05-10 07:45:42 -04:00
$body [] = ' * ' . sprintf ( __ ( 'SUCCESS: %s' ), $name );
}
}
if ( $success_items != $this -> update_results [ $type ] ) {
// Failed updates
$messages = array (
'plugin' => __ ( 'The following plugins failed to update:' ),
'theme' => __ ( 'The following themes failed to update:' ),
'translation' => __ ( 'The following translations failed to update:' ),
);
$body [] = $messages [ $type ];
foreach ( $this -> update_results [ $type ] as $item ) {
if ( ! $item -> result || is_wp_error ( $item -> result ) ) {
2018-08-30 09:36:25 -04:00
/* translators: %s: name of plugin / theme / translations */
2016-05-10 07:45:42 -04:00
$body [] = ' * ' . sprintf ( __ ( 'FAILED: %s' ), $item -> name );
$failures ++ ;
}
}
}
$body [] = '' ;
}
$site_title = wp_specialchars_decode ( get_bloginfo ( 'name' ), ENT_QUOTES );
if ( $failures ) {
2017-11-30 18:11:00 -05:00
$body [] = trim (
__ (
" BETA TESTING?
2016-05-10 07:45:42 -04:00
=============
This debugging email is sent when you are using a development version of WordPress .
If you think these failures might be due to a bug in WordPress , could you report it ?
* Open a thread in the support forums : https :// wordpress . org / support / forum / alphabeta
* Or , if you ' re comfortable writing a bug report : https :// core . trac . wordpress . org /
2017-11-30 18:11:00 -05:00
Thanks ! -- The WordPress Team "
)
);
2016-05-10 07:45:42 -04:00
$body [] = '' ;
2019-04-15 18:39:50 -04:00
/* translators: Background update failed notification email subject. %s: Site title */
2019-04-08 02:17:51 -04:00
$subject = sprintf ( __ ( '[%s] Background Update Failed' ), $site_title );
2016-05-10 07:45:42 -04:00
} else {
2019-04-15 18:39:50 -04:00
/* translators: Background update finished notification email subject. %s: Site title */
2019-04-08 02:17:51 -04:00
$subject = sprintf ( __ ( '[%s] Background Update Finished' ), $site_title );
2016-05-10 07:45:42 -04:00
}
2017-11-30 18:11:00 -05:00
$body [] = trim (
__ (
' UPDATE LOG
========== '
)
);
2016-05-10 07:45:42 -04:00
$body [] = '' ;
foreach ( array ( 'core' , 'plugin' , 'theme' , 'translation' ) as $type ) {
2017-11-30 18:11:00 -05:00
if ( ! isset ( $this -> update_results [ $type ] ) ) {
2016-05-10 07:45:42 -04:00
continue ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
foreach ( $this -> update_results [ $type ] as $update ) {
$body [] = $update -> name ;
$body [] = str_repeat ( '-' , strlen ( $update -> name ) );
2017-11-30 18:11:00 -05:00
foreach ( $update -> messages as $message ) {
$body [] = ' ' . html_entity_decode ( str_replace ( '…' , '...' , $message ) );
}
2016-05-10 07:45:42 -04:00
if ( is_wp_error ( $update -> result ) ) {
$results = array ( 'update' => $update -> result );
// If we rolled back, we want to know an error that occurred then too.
2017-11-30 18:11:00 -05:00
if ( 'rollback_was_required' === $update -> result -> get_error_code () ) {
2016-05-10 07:45:42 -04:00
$results = ( array ) $update -> result -> get_error_data ();
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
foreach ( $results as $result_type => $result ) {
2017-11-30 18:11:00 -05:00
if ( ! is_wp_error ( $result ) ) {
2016-05-10 07:45:42 -04:00
continue ;
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
if ( 'rollback' === $result_type ) {
/* translators: 1: Error code, 2: Error message. */
$body [] = ' ' . sprintf ( __ ( 'Rollback Error: [%1$s] %2$s' ), $result -> get_error_code (), $result -> get_error_message () );
} else {
/* translators: 1: Error code, 2: Error message. */
$body [] = ' ' . sprintf ( __ ( 'Error: [%1$s] %2$s' ), $result -> get_error_code (), $result -> get_error_message () );
}
2017-11-30 18:11:00 -05:00
if ( $result -> get_error_data () ) {
2016-05-10 07:45:42 -04:00
$body [] = ' ' . implode ( ', ' , ( array ) $result -> get_error_data () );
2017-11-30 18:11:00 -05:00
}
2016-05-10 07:45:42 -04:00
}
}
$body [] = '' ;
}
}
$email = array (
'to' => get_site_option ( 'admin_email' ),
'subject' => $subject ,
'body' => implode ( " \n " , $body ),
2017-11-30 18:11:00 -05:00
'headers' => '' ,
2016-05-10 07:45:42 -04:00
);
/**
2016-05-22 14:01:30 -04:00
* Filters the debug email that can be sent following an automatic
2016-05-10 07:45:42 -04:00
* background core update .
*
* @ since 3.8 . 0
*
* @ param array $email {
* Array of email arguments that will be passed to wp_mail () .
*
* @ type string $to The email recipient . An array of emails
* can be returned , as handled by wp_mail () .
* @ type string $subject Email subject .
* @ type string $body Email message body .
* @ type string $headers Any email headers . Default empty .
* }
* @ param int $failures The number of failures encountered while upgrading .
* @ param mixed $results The results of all attempted updates .
*/
$email = apply_filters ( 'automatic_updates_debug_email' , $email , $failures , $this -> update_results );
wp_mail ( $email [ 'to' ], wp_specialchars_decode ( $email [ 'subject' ] ), $email [ 'body' ], $email [ 'headers' ] );
}
}