From 63956aa479b2abc2bb822085bc7b63373d047921 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Fri, 13 Sep 2013 06:18:16 +0000 Subject: [PATCH] WordPress Core Automatic Updates: Add the first slice of Automatic Upgrades, This is presently disabled, and requires a filter to enable ( 'auto_upgrade_core' ). See #22704 git-svn-id: https://develop.svn.wordpress.org/trunk@25421 602fd350-edb4-49c9-b593-d223f7449a82 --- .../includes/class-wp-upgrader-skins.php | 4 +- src/wp-admin/includes/class-wp-upgrader.php | 275 ++++++++++++++++++ src/wp-admin/includes/update.php | 30 ++ src/wp-includes/update.php | 54 ++++ 4 files changed, 361 insertions(+), 2 deletions(-) diff --git a/src/wp-admin/includes/class-wp-upgrader-skins.php b/src/wp-admin/includes/class-wp-upgrader-skins.php index 5dd2ce228a..fcc63fc9ba 100644 --- a/src/wp-admin/includes/class-wp-upgrader-skins.php +++ b/src/wp-admin/includes/class-wp-upgrader-skins.php @@ -529,7 +529,7 @@ class Theme_Upgrader_Skin extends WP_Upgrader_Skin { } /** - * Upgrader Skin for Background WordPress Upgrades + * Upgrader Skin for Automatic WordPress Upgrades * * This skin is designed to be used when no output is intended, all output * is captured and stored for the caller to process and log/email/discard. @@ -538,7 +538,7 @@ class Theme_Upgrader_Skin extends WP_Upgrader_Skin { * @subpackage Upgrader * @since 3.7.0 */ -class Background_Upgrader_Skin extends WP_Upgrader_Skin { +class Automatic_Upgrader_Skin extends WP_Upgrader_Skin { var $messages = array(); function request_filesystem_credentials( $error = false ) { diff --git a/src/wp-admin/includes/class-wp-upgrader.php b/src/wp-admin/includes/class-wp-upgrader.php index b9f66a009c..92f767e20d 100644 --- a/src/wp-admin/includes/class-wp-upgrader.php +++ b/src/wp-admin/includes/class-wp-upgrader.php @@ -1137,6 +1137,61 @@ class Core_Upgrader extends WP_Upgrader { return $result; } + // Determines if this WordPress Core version should update to $offered_ver or not + static function should_upgrade_to_version( $offered_ver /* x.y.z */ ) { + include ABSPATH . WPINC . '/version.php'; // $wp_version; // x.y.z + + $current_branch = implode( '.', array_slice( preg_split( '/[.-]/', $wp_version ), 0, 2 ) ); // x.y + $new_branch = implode( '.', array_slice( preg_split( '/[.-]/', $offered_ver ), 0, 2 ) ); // x.y + $current_is_development_version = (bool) strpos( $wp_version, '-' ); + + // Defaults: + $upgrade_dev = false; + $upgrade_minor = false; // @TODO: Update for release by toggling to true. + $upgrade_major = false; + + // WP_AUTO_UPDATE_CORE = true (all), 'minor', false. + if ( defined( 'WP_AUTO_UPDATE_CORE' ) ) { + if ( false === WP_AUTO_UPDATE_CORE ) { + // Defaults to turned off, unless a filter allows it + $upgrade_dev = $upgrade_minor = $upgrade_major = false; + } elseif ( true === WP_AUTO_UPDATE_CORE ) { + // ALL updates for core + $upgrade_dev = $upgrade_minor = $upgrade_major = true; + } elseif ( 'minor' === WP_AUTO_UPDATE_CORE ) { + // Only minor updates for core + $upgrade_dev = $upgrade_major = false; + $upgrade_minor = true; + } + } + + // 1: If we're already on that version, not much point in updating? + if ( $offered_ver == $wp_version ) + return false; + + // 2: If we're running a newer version, that's a nope + if ( version_compare( $wp_version, $offered_ver, '>=' ) ) + return false; + + // 3: 3.7-alpha-25000 -> 3.7-alpha-25678 -> 3.7-beta1 -> 3.7-beta2 + if ( $current_is_development_version ) { + if ( ! apply_filters( 'allow_dev_auto_core_updates', $upgrade_dev ) ) + return false; + // else fall through to minor + major branches below + } + + // 4: Minor In-branch updates (3.7.0 -> 3.7.1 -> 3.7.2 -> 3.7.4) + if ( $current_branch == $new_branch ) + return apply_filters( 'allow_minor_auto_core_updates', $upgrade_minor ); + + // 5: Major version updates (3.7.0 -> 3.8.0 -> 3.9.1) + if ( version_compare( $new_branch, $current_branch, '>' ) ) + return apply_filters( 'allow_major_auto_core_updates', $upgrade_major ); + + // If we're not sure, we don't want it + return false; + } + } /** @@ -1212,3 +1267,223 @@ class File_Upload_Upgrader { return true; } } + +/** + * WordPress Automatic Upgrader helper class + * + * @since 3.7.0 + */ +class WP_Automatic_Upgrader { + + static $skin; + + static function upgrader_disabled() { + // That's a no if you don't want files changes + if ( defined( 'DISABLE_FILE_MODS' ) && DISABLE_FILE_MODS ) + return true; + + // More fine grained control can be done through the WP_AUTO_UPDATE_CORE constant and filters + if ( defined( 'AUTOMATIC_UPDATER_DISABLED' ) && AUTOMATIC_UPDATER_DISABLED ) + return true; + + if ( defined( 'WP_INSTALLING' ) ) + return true; + + return apply_filters( 'auto_upgrader_disabled', false ); + } + + /** + * Tests to see if we should upgrade a specific item, does not test to see if we CAN update the item. + */ + static function should_auto_update( $type, $item, $context ) { + + if ( self::upgrader_disabled() ) + return false; + + // ..and also check for GIT/SVN checkouts + if ( ! apply_filters( 'auto_upgrade_ignore_checkout_status', false ) ) { + $stop_dirs = array( + ABSPATH, + untrailingslashit( $context ), + ); + if ( ! file_exists( ABSPATH . '/wp-config.php' ) ) // wp-config.php up one folder in a deployment situation + $stop_dirs[] = dirname( ABSPATH ); + foreach ( array_unique( $stop_dirs ) as $dir ) { + if ( file_exists( $dir . '/.svn' ) || file_exists( $dir . '/.git' ) ) + return false; + } + } + + // Next up, do we actually have it enabled for this type of update? + switch ( $type ) { + case 'core': + $upgrade = Core_Upgrader::should_upgrade_to_version( $item->current ); + break; + default: + case 'plugin': + case 'theme': + $upgrade = false; + break; + } + + // And does the user / plugins want it? + // Plugins may filter on 'auto_upgrade_plugin', and check the 2nd param, $item, to only enable it for certain Plugins/Themes + if ( ! apply_filters( 'auto_upgrade_' . $type, $upgrade, $item ) ) + return false; + + // If it's a core update, are we actually compatible with it's requirements? + if ( 'core' == $type ) { + global $wpdb; + + $php_compat = version_compare( phpversion(), $item->php_version, '>=' ); + if ( file_exists( WP_CONTENT_DIR . '/db.php' ) && empty( $wpdb->is_mysql ) ) + $mysql_compat = true; + else + $mysql_compat = version_compare( $wpdb->db_version(), $item->mysql_version, '>=' ); + + if ( ! $php_compat || ! $mysql_compat ) + return false; + } + + return true; + } + + // Checks to see if WP_Filesystem is setup to allow unattended upgrades + static function can_auto_update( $context ) { + if ( ! self::$skin ) + self::$skin = new Automatic_Upgrader_Skin(); + return (bool) self::$skin->request_filesystem_credentials(); + } + + static function upgrade( $type, $item ) { + + self::$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', function( $message ) { + WP_Background_Upgrader::$skin->feedback( $message ); + return $message; + } ); + $upgrader = new Core_Upgrader( self::$skin ); + $context = ABSPATH; + break; + case 'plugin': + $upgrader = new Plugin_Upgrader( self::$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( self::$skin ); + $context = get_theme_root( $item ); + break; + } + + // Determine if we can perform this upgrade or not + if ( ! self::should_auto_update( $type, $item, $context ) || ! self::can_auto_update( $context ) ) + return false; + + /*wp_mail( + get_site_option( 'admin_email' ), + __METHOD__, + "Starting an upgrade for:\n\n" . var_export( compact( 'type', 'item' ), true ) . "\n\n" . wp_debug_backtrace_summary() + );*/ + + // Boom, This sites about to get a whole new splash of paint! + $upgrade_result = $upgrader->upgrade( $item, array( + 'clear_update_cache' => false, + ) ); + + // Core doesn't output this, so lets append it so we don't get confused + if ( 'core' == $type ) { + if ( is_wp_error( $upgrade_result ) ) { + self::$skin->error( __( 'Installation Failed' ), $upgrade_result ); + } else { + self::$skin->feedback( __( 'WordPress updated successfully' ) ); + } + } + + // Clear cache's and transients + switch ( $type ) { + case 'core': + delete_site_transient( 'update_core' ); + break; + case 'theme': + wp_clean_themes_cache(); + break; + case 'plugin': + wp_clean_plugins_cache(); + break; + } + + //var_dump( compact( 'type', 'item', 'upgrader', 'upgrade_result' ) ); + + wp_mail( + get_site_option( 'admin_email' ), + __METHOD__, + var_export( array( + $upgrade_result, + $upgrader, + self::$skin, + ), true ) + ); + + return $upgrade_result; + } + + /** + * Kicks off a upgrade request for each item in the upgrade "queue" + */ + static function perform_auto_updates() { + + $lock_name = 'auto_upgrader.lock'; + if ( get_site_transient( $lock_name ) ) { + // Test to see if it was set more than an hour ago, if so, cleanup. + if ( true || get_site_transient( $lock_name ) < ( time() - HOUR_IN_SECONDS ) ) + delete_site_transient( $lock_name ); + else // Recent lock + return; + } + // Lock upgrades for us for half an hour + if ( ! set_site_transient( $lock_name, microtime( true ), HOUR_IN_SECONDS / 2 ) ) + return; + + // Next, Plugins + wp_update_plugins(); // Check for Plugin updates + $plugin_updates = get_site_transient( 'update_plugins' ); + if ( $plugin_updates && !empty( $plugin_updates->response ) ) { + foreach ( array_keys( $plugin_updates->response ) as $plugin ) { + self::upgrade( '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' ); + if ( $theme_updates && !empty( $theme_updates->response ) ) { + foreach ( array_keys( $theme_updates->response ) as $theme ) { + self::upgrade( 'theme', $theme ); + } + // Force refresh of theme update information + wp_clean_themes_cache(); + } + + // Finally, Process any core upgrade + wp_version_check(); // Check for Core updates + $core_update = find_core_auto_update(); + if ( $core_update ) + self::upgrade( 'core', $core_update ); + + // Cleanup, These won't trigger any updates this time due to the locking transient + wp_version_check(); // check for Core updates + wp_update_themes(); // Check for Theme updates + wp_update_plugins(); // Check for Plugin updates + + // TODO The core database upgrade has already cleared this transient.. + delete_site_transient( $lock_name ); + + } + +} \ No newline at end of file diff --git a/src/wp-admin/includes/update.php b/src/wp-admin/includes/update.php index a39bddac00..dc1f5b41f9 100644 --- a/src/wp-admin/includes/update.php +++ b/src/wp-admin/includes/update.php @@ -57,6 +57,36 @@ function get_core_updates( $options = array() ) { return $result; } +/** + * Gets the best available (and enabled) Auto-Update for WordPress Core. + * + * If there's 1.2.3 and 1.3 on offer, it'll choose 1.3 if the install allows it, else, 1.2.3 + * + * @since 3.7.0 + * + * @return bool|array False on failure, otherwise the core update offering. + */ +function find_core_auto_update() { + $updates = get_site_transient( 'update_core' ); + if ( ! $updates || empty( $updates->updates ) ) + return false; + + include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; + + $auto_update = false; + foreach ( $updates->updates as $update ) { + if ( 'autoupdate' != $update->response ) + continue; + + if ( ! WP_Automatic_Upgrader::should_auto_update( 'core', $update, ABSPATH ) ) + continue; + + if ( ! $auto_update || version_compare( $update->current, $auto_update->current, '>' ) ) + $auto_update = $update; + } + return $auto_update; +} + function dismiss_core_update( $update ) { $dismissed = get_site_option( 'dismissed_update_core' ); $dismissed[ $update->current . '|' . $update->locale ] = true; diff --git a/src/wp-includes/update.php b/src/wp-includes/update.php index 7c426a13e9..2123387bb4 100644 --- a/src/wp-includes/update.php +++ b/src/wp-includes/update.php @@ -120,6 +120,8 @@ function wp_version_check() { $updates->last_checked = time(); $updates->version_checked = $wp_version; set_site_transient( 'update_core', $updates); + + wp_auto_updates_maybe_queue( 'core' ); } /** @@ -221,6 +223,8 @@ function wp_update_plugins() { $new_option->response = array(); set_site_transient( 'update_plugins', $new_option ); + + wp_auto_updates_maybe_queue( 'plugins' ); } /** @@ -331,6 +335,53 @@ function wp_update_themes() { $new_update->response = $response; set_site_transient( 'update_themes', $new_update ); + + wp_auto_updates_maybe_queue( 'themes' ); +} + +/** + * Queues a cron entry if a potentially upgrade is detected. + * + * @since 3.7.0 + * + * @param string $type The type of update to check for, may be 'core', 'plugins', or, 'themes'. + */ +function wp_auto_updates_maybe_queue( $type = 'core' ) { + include_once ABSPATH . '/wp-admin/includes/class-wp-upgrader.php'; + include_once ABSPATH . '/wp-admin/includes/update.php'; + + if ( WP_Automatic_Upgrader::upgrader_disabled() ) + return; + + $updates_available = false; + if ( 'core' == $type ) { + $updates_available = (bool) find_core_auto_update(); + } elseif ( 'plugins' == $type ) { + $plugin_updates = get_site_transient( 'update_plugins' ); + $updates_available = !empty( $plugin_updates->response ); + } elseif ( 'themes' == $type ) { + $theme_updates = get_site_transient( 'update_themes' ); + $updates_available = empty( $theme_updates->response ); + } + + if ( $updates_available && ! wp_next_scheduled( 'wp_auto_updates_execute' ) ) { + // If the transient update was triggered by a user pageview, update in an hours time, else, now. + $when_to_update = get_current_user_id() ? time() + HOUR_IN_SECONDS : time(); + $when_to_update = apply_filters( 'auto_upgrade_when_to_upgrade', $when_to_update ); + + wp_schedule_single_event( $when_to_update, 'wp_auto_updates_execute' ); + } + +} + +function wp_auto_updates_execute() { + include_once ABSPATH . '/wp-admin/includes/admin.php'; + include_once ABSPATH . '/wp-admin/includes/class-wp-upgrader.php'; + + if ( WP_Automatic_Upgrader::upgrader_disabled() ) + return; + + WP_Automatic_Upgrader::perform_auto_updates(); } /* @@ -456,4 +507,7 @@ add_action( 'load-update-core.php', 'wp_update_themes' ); add_action( 'admin_init', '_maybe_update_themes' ); add_action( 'wp_update_themes', 'wp_update_themes' ); +// Automatic Updates - Cron callback +add_action( 'wp_auto_updates_execute', 'wp_auto_updates_execute' ); + add_action('init', 'wp_schedule_update_checks');