| 1 | <?php
|
|---|
| 2 |
|
|---|
| 3 | class WP_Background_Upgrader {
|
|---|
| 4 |
|
|---|
| 5 | static $skin;
|
|---|
| 6 |
|
|---|
| 7 | private function __construct() {}
|
|---|
| 8 | static function init() {
|
|---|
| 9 |
|
|---|
| 10 | // Detect updates for Core, Plugins, and, Themes
|
|---|
| 11 | add_action( 'set_site_transient_' . 'update_core', array( 'WP_Background_Upgrader', 'queue_updates_check_core' ) );
|
|---|
| 12 | add_action( 'set_site_transient_' . 'update_plugins', array( 'WP_Background_Upgrader', 'queue_updates_check_plugins_themes' ) );
|
|---|
| 13 | add_action( 'set_site_transient_' . 'update_themes', array( 'WP_Background_Upgrader', 'queue_updates_check_plugins_themes' ) );
|
|---|
| 14 | // Uh.. Yeah, work around for #25213
|
|---|
| 15 | add_action( 'set_site_transient_' . '_site_transient_' . 'update_core', array( 'WP_Background_Upgrader', 'queue_updates_check' ) );
|
|---|
| 16 | add_action( 'set_site_transient_' . '_site_transient_' . 'update_plugins', array( 'WP_Background_Upgrader', 'queue_updates_check' ) );
|
|---|
| 17 | add_action( 'set_site_transient_' . '_site_transient_' . 'update_themes', array( 'WP_Background_Upgrader', 'queue_updates_check' ) );
|
|---|
| 18 |
|
|---|
| 19 | // Cron Updates
|
|---|
| 20 | add_action( 'wp_background_upgrader_process', array( 'WP_Background_Upgrader', 'perform_queued_updates' ) );
|
|---|
| 21 |
|
|---|
| 22 | // Temporary way of testing to see if the install is fataling
|
|---|
| 23 | // Used with WP_Background_Upgrader::test_if_site_is_ok()
|
|---|
| 24 | add_action( 'template_redirect', array( 'WP_Background_Upgrader','test_if_site_is_ok_callback' ) , 1 );
|
|---|
| 25 |
|
|---|
| 26 | }
|
|---|
| 27 |
|
|---|
| 28 | /**
|
|---|
| 29 | * Loads the WordPress Admin environment for performing the updates.
|
|---|
| 30 | * Needed because cron runs as a front-end process
|
|---|
| 31 | */
|
|---|
| 32 | static function load_required_admin_includes() {
|
|---|
| 33 | // include ABSPATH . 'wp-admin/includes/admin.php'; // Load the whole lot of 'em
|
|---|
| 34 | include ABSPATH . 'wp-admin/includes/update.php';
|
|---|
| 35 | include ABSPATH . 'wp-admin/includes/plugin.php';
|
|---|
| 36 | include ABSPATH . 'wp-admin/includes/plugin-install.php';
|
|---|
| 37 | include ABSPATH . 'wp-admin/includes/theme.php';
|
|---|
| 38 | include ABSPATH . 'wp-admin/includes/theme-install.php';
|
|---|
| 39 | include ABSPATH . 'wp-admin/includes/file.php';
|
|---|
| 40 | include ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
|---|
| 41 | }
|
|---|
| 42 |
|
|---|
| 43 | /**
|
|---|
| 44 | * Finds the best auto-update available for this site.
|
|---|
| 45 | * 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
|
|---|
| 46 | */
|
|---|
| 47 | static function find_core_auto_update() {
|
|---|
| 48 | $updates = get_site_transient( 'update_core' );
|
|---|
| 49 | $auto_update = false;
|
|---|
| 50 | foreach ( $updates->updates as $update ) {
|
|---|
| 51 | if ( 'autoupdate' != $update->response )
|
|---|
| 52 | continue;
|
|---|
| 53 |
|
|---|
| 54 | if ( ! self::should_upgrade( 'core', $update, ABSPATH ) )
|
|---|
| 55 | continue;
|
|---|
| 56 |
|
|---|
| 57 | if ( ! $auto_update || version_compare( $update->current, $auto_update->current, '>' ) )
|
|---|
| 58 | $auto_update = $update;
|
|---|
| 59 | }
|
|---|
| 60 | return $auto_update;
|
|---|
| 61 | }
|
|---|
| 62 |
|
|---|
| 63 | static function upgrader_disabled() {
|
|---|
| 64 | // That's a no if you don't want files changes
|
|---|
| 65 | if ( defined( 'DISABLE_FILE_MODS' ) && DISABLE_FILE_MODS )
|
|---|
| 66 | return true;
|
|---|
| 67 |
|
|---|
| 68 | if ( defined( 'AUTOMATIC_UPDATER_DISABLED' ) && AUTOMATIC_UPDATER_DISABLED )
|
|---|
| 69 | return true;
|
|---|
| 70 |
|
|---|
| 71 | return apply_filters( 'auto_upgrader_disabled', false );
|
|---|
| 72 | }
|
|---|
| 73 |
|
|---|
| 74 | /**
|
|---|
| 75 | * Tests to see if we should upgrade a specific item, does not test to see if we CAN update the item.
|
|---|
| 76 | */
|
|---|
| 77 | static function should_upgrade( $type, $item, $context ) {
|
|---|
| 78 |
|
|---|
| 79 | if ( self::upgrader_disabled() )
|
|---|
| 80 | return false;
|
|---|
| 81 |
|
|---|
| 82 | // ..and also check for GIT/SVN checkouts
|
|---|
| 83 | if ( ! apply_filters( 'auto_upgrade_ignore_checkout_status', false ) ) {
|
|---|
| 84 | $stop_dirs = array(
|
|---|
| 85 | ABSPATH,
|
|---|
| 86 | untrailingslashit( $context ),
|
|---|
| 87 | );
|
|---|
| 88 | if ( ! file_exists( ABSPATH . '/wp-config.php' ) ) // wp-config.php up one folder in a deployment situation
|
|---|
| 89 | $stop_dirs[] = dirname( ABSPATH );
|
|---|
| 90 | foreach ( array_unique( $stop_dirs ) as $dir ) {
|
|---|
| 91 | if ( file_exists( $dir . '/.svn' ) || file_exists( $dir . '/.git' ) )
|
|---|
| 92 | return false;
|
|---|
| 93 | }
|
|---|
| 94 | }
|
|---|
| 95 |
|
|---|
| 96 | // Next up, do we actually have it enabled for this type of update?
|
|---|
| 97 | switch ( $type ) {
|
|---|
| 98 | default: $upgrade = false; break;
|
|---|
| 99 | case 'core':
|
|---|
| 100 | $upgrade = self::should_upgrade_to_core_version( $item->current );
|
|---|
| 101 | break;
|
|---|
| 102 | case 'plugin': $upgrade = false; break;
|
|---|
| 103 | case 'theme': $upgrade = false; break;
|
|---|
| 104 | case 'lang': $upgrade = false; break;
|
|---|
| 105 | }
|
|---|
| 106 |
|
|---|
| 107 | // And does the user / plugins want it?
|
|---|
| 108 | // Plugins may filter on 'auto_upgrade_plugin', and check the 2nd param, $item, to only enable it for certain Plugins/Themes
|
|---|
| 109 | if ( ! apply_filters( 'auto_upgrade_' . $type, $upgrade, $item ) )
|
|---|
| 110 | return false;
|
|---|
| 111 |
|
|---|
| 112 | // If it's a core update, are we actually compatible with it's requirements?
|
|---|
| 113 | if ( 'core' == $type ) {
|
|---|
| 114 | global $wpdb;
|
|---|
| 115 |
|
|---|
| 116 | $php_compat = version_compare( phpversion(), $item->php_version, '>=' );
|
|---|
| 117 | if ( file_exists( WP_CONTENT_DIR . '/db.php' ) && empty( $wpdb->is_mysql ) )
|
|---|
| 118 | $mysql_compat = true;
|
|---|
| 119 | else
|
|---|
| 120 | $mysql_compat = version_compare( $wpdb->db_version(), $item->mysql_version, '>=' );
|
|---|
| 121 |
|
|---|
| 122 | if ( ! $php_compat || ! $mysql_compat )
|
|---|
| 123 | return false;
|
|---|
| 124 | }
|
|---|
| 125 |
|
|---|
| 126 | // Make sure loop-back HTTP requests work, we need to be able to verify everything worked OK and to be able to upgrade the database
|
|---|
| 127 | // If this doesn't pass, chances are they don't want auto-updates anyway
|
|---|
| 128 | if ( ! self::test_if_site_is_ok() )
|
|---|
| 129 | return false;
|
|---|
| 130 |
|
|---|
| 131 | return true;
|
|---|
| 132 | }
|
|---|
| 133 |
|
|---|
| 134 | // Checks to see if WP_Filesystem is setup to allow unattended upgrades
|
|---|
| 135 | static function can_upgrade( $context ) {
|
|---|
| 136 | if ( ! self::$skin )
|
|---|
| 137 | self::$skin = new WP_Background_Upgrader_Skin();
|
|---|
| 138 | return (bool) self::$skin->request_filesystem_credentials();
|
|---|
| 139 | }
|
|---|
| 140 |
|
|---|
| 141 | // Determines if this WordPress Core version should update to $offered_ver or not
|
|---|
| 142 | static function should_upgrade_to_core_version( $offered_ver /* x.y.z */ ) {
|
|---|
| 143 | include ABSPATH . WPINC . '/version.php'; // $wp_version; // x.y.z
|
|---|
| 144 |
|
|---|
| 145 | $current_branch = implode( '.', array_slice( preg_split( '/[.-]/', $wp_version ), 0, 2 ) ); // x.y
|
|---|
| 146 | $new_branch = implode( '.', array_slice( preg_split( '/[.-]/', $offered_ver ), 0, 2 ) ); // x.y
|
|---|
| 147 | $current_is_development_version = (bool) strpos( $wp_version, '-' );
|
|---|
| 148 |
|
|---|
| 149 | // Defaults:
|
|---|
| 150 | $upgrade_dev = false;
|
|---|
| 151 | $upgrade_minor = true;
|
|---|
| 152 | $upgrade_major = false;
|
|---|
| 153 |
|
|---|
| 154 | // WP_AUTO_UPDATE_CORE = true (all), 'minor', false.
|
|---|
| 155 | if ( defined( 'WP_AUTO_UPDATE_CORE' ) ) {
|
|---|
| 156 | if ( false === WP_AUTO_UPDATE_CORE ) {
|
|---|
| 157 | // Defaults to turned off, unless a filter allows it
|
|---|
| 158 | $upgrade_dev = $upgrade_minor = $upgrade_major = false;
|
|---|
| 159 | } elseif ( true === WP_AUTO_UPDATE_CORE ) {
|
|---|
| 160 | // ALL updates for core
|
|---|
| 161 | $upgrade_dev = $upgrade_minor = $upgrade_major = true;
|
|---|
| 162 | } elseif ( 'minor' === WP_AUTO_UPDATE_CORE ) {
|
|---|
| 163 | // Only minor updates for core
|
|---|
| 164 | $upgrade_dev = $upgrade_major = false;
|
|---|
| 165 | $upgrade_minor = true;
|
|---|
| 166 | }
|
|---|
| 167 | }
|
|---|
| 168 |
|
|---|
| 169 | // 1: If we're already on that version, not much point in updating?
|
|---|
| 170 | if ( $offered_ver == $wp_version )
|
|---|
| 171 | return false;
|
|---|
| 172 |
|
|---|
| 173 | // 2: If we're running a newer version, that's a nope
|
|---|
| 174 | if ( version_compare( $wp_version, $offered_ver, '>=' ) )
|
|---|
| 175 | return false;
|
|---|
| 176 |
|
|---|
| 177 | // 3: 3.7-alpha-25000 -> 3.7-alpha-25678 -> 3.7-beta1 -> 3.7-beta2
|
|---|
| 178 | if ( $current_is_development_version ) {
|
|---|
| 179 | if ( ! apply_filters( 'allow_dev_background_core_updates', $upgrade_dev ) )
|
|---|
| 180 | return false;
|
|---|
| 181 | // else fall through to minor + major branches below
|
|---|
| 182 | }
|
|---|
| 183 |
|
|---|
| 184 | // 4: Minor In-branch updates (3.7.0 -> 3.7.1 -> 3.7.2 -> 3.7.4)
|
|---|
| 185 | if ( $current_branch == $new_branch )
|
|---|
| 186 | return apply_filters( 'allow_minor_background_core_updates', $upgrade_minor );
|
|---|
| 187 |
|
|---|
| 188 | // 5: Major version updates (3.7.0 -> 3.8.0 -> 3.9.1)
|
|---|
| 189 | if ( version_compare( $new_branch, $current_branch, '>' ) )
|
|---|
| 190 | return apply_filters( 'allow_major_background_core_updates', $upgrade_major );
|
|---|
| 191 |
|
|---|
| 192 | // If we're not sure, we don't want it
|
|---|
| 193 | return false;
|
|---|
| 194 | }
|
|---|
| 195 |
|
|---|
| 196 | static function upgrade( $type, $item ) {
|
|---|
| 197 |
|
|---|
| 198 | wp_mail(
|
|---|
| 199 | get_site_option( 'admin_email' ),
|
|---|
| 200 | __METHOD__,
|
|---|
| 201 | "Starting an upgrade for:\n\n" . var_export( compact( 'type', 'item' ), true ) . "\n\n" . wp_debug_backtrace_summary()
|
|---|
| 202 | );
|
|---|
| 203 |
|
|---|
| 204 | self::$skin = new Background_Upgrader_Skin();
|
|---|
| 205 |
|
|---|
| 206 | switch( $type ) {
|
|---|
| 207 | case 'core':
|
|---|
| 208 | // Okay, Why does the Core upgrader not use the Upgrader's skin during the actual main part of the upgrade???
|
|---|
| 209 | add_filter( 'update_feedback', function( $message ) {
|
|---|
| 210 | WP_Background_Upgrader::$skin->feedback( $message );
|
|---|
| 211 | } );
|
|---|
| 212 | $upgrader = new Core_Upgrader( self::$skin );
|
|---|
| 213 | $context = ABSPATH;
|
|---|
| 214 | break;
|
|---|
| 215 | case 'plugin':
|
|---|
| 216 | $upgrader = new Plugin_Upgrader( self::$skin );
|
|---|
| 217 | $context = WP_PLUGIN_DIR; // We don't support custom Plugin directories, or updates for WPMU_PLUGIN_DIR
|
|---|
| 218 | break;
|
|---|
| 219 | case 'theme':
|
|---|
| 220 | $upgrader = new Theme_Upgrader( self::$skin );
|
|---|
| 221 | $context = get_theme_root( $item );
|
|---|
| 222 | break;
|
|---|
| 223 | case 'lang':
|
|---|
| 224 | return false; // Not quite yet!
|
|---|
| 225 | // $upgrader = new Language_Upgrader( self::$skin );
|
|---|
| 226 | $context = WP_LANG_DIR;
|
|---|
| 227 | break;
|
|---|
| 228 | }
|
|---|
| 229 |
|
|---|
| 230 | // Determine if we can perform this upgrade or not
|
|---|
| 231 | if ( ! self::should_upgrade( $type, $item, $context ) || ! self::can_upgrade( $context ) )
|
|---|
| 232 | return false;
|
|---|
| 233 |
|
|---|
| 234 | // Boom, This sites about to get a whole new splash of paint!
|
|---|
| 235 | $upgrade_result = $upgrader->upgrade( $item, array(
|
|---|
| 236 | 'clear_update_cache' => false,
|
|---|
| 237 | ) );
|
|---|
| 238 |
|
|---|
| 239 | // Core doesn't output this, so lets append it so we don't get confused
|
|---|
| 240 | if ( 'core' == $type ) {
|
|---|
| 241 | if ( is_wp_error( $upgrade_result ) ) {
|
|---|
| 242 | self::$skin->error( __( 'Installation Failed' ), $upgrade_result );
|
|---|
| 243 | } else {
|
|---|
| 244 | self::$skin->feedback( __( 'WordPress updated successfully' ) );
|
|---|
| 245 | }
|
|---|
| 246 | }
|
|---|
| 247 |
|
|---|
| 248 | // Clear cache's and update results
|
|---|
| 249 | switch ( $type ) {
|
|---|
| 250 | case 'core':
|
|---|
| 251 | delete_site_transient( 'update_core' );
|
|---|
| 252 | break;
|
|---|
| 253 | case 'theme':
|
|---|
| 254 | wp_clean_themes_cache();
|
|---|
| 255 | break;
|
|---|
| 256 | case 'plugin':
|
|---|
| 257 | wp_clean_plugins_cache();
|
|---|
| 258 | break;
|
|---|
| 259 | }
|
|---|
| 260 |
|
|---|
| 261 | var_dump( compact( 'type', 'item', 'upgrader', 'upgrade_result' ) );
|
|---|
| 262 |
|
|---|
| 263 | wp_mail(
|
|---|
| 264 | get_site_option( 'admin_email' ),
|
|---|
| 265 | __METHOD__,
|
|---|
| 266 | var_export( array(
|
|---|
| 267 | $upgrade_result,
|
|---|
| 268 | $upgrader,
|
|---|
| 269 | self::$skin,
|
|---|
| 270 | ), true )
|
|---|
| 271 | );
|
|---|
| 272 |
|
|---|
| 273 | return $upgrade_result;
|
|---|
| 274 | }
|
|---|
| 275 |
|
|---|
| 276 | /**
|
|---|
| 277 | * Queues a cron entry in the event that the automatic upgrader is enabled, and there's at least one potentially upgradable item
|
|---|
| 278 | */
|
|---|
| 279 | static function queue_updates_check( $updates ) {
|
|---|
| 280 |
|
|---|
| 281 | if ( self::upgrader_disabled() )
|
|---|
| 282 | return;
|
|---|
| 283 |
|
|---|
| 284 | $plugin_updates = get_site_transient( 'update_plugins' );
|
|---|
| 285 | $theme_updates = get_site_transient( 'update_themes' );
|
|---|
| 286 | if ( ! self::find_core_auto_update() && empty( $plugin_updates->response ) && empty( $theme_updates->response ) )
|
|---|
| 287 | return;
|
|---|
| 288 |
|
|---|
| 289 | if ( ! wp_next_scheduled( 'wp_background_upgrader_process' ) ) {
|
|---|
| 290 | // If the transient update was triggered by a user pageview, update in an hours time, else, now.
|
|---|
| 291 | $when_to_update = get_current_user_id() ? time() + HOUR_IN_SECONDS : time();
|
|---|
| 292 | $when_to_update = apply_filters( 'auto_upgrade_when_to_upgrade', $when_to_update );
|
|---|
| 293 |
|
|---|
| 294 | wp_schedule_single_event( $when_to_update, 'wp_background_upgrader_process' );
|
|---|
| 295 | }
|
|---|
| 296 |
|
|---|
| 297 | }
|
|---|
| 298 |
|
|---|
| 299 | /**
|
|---|
| 300 | * Kicks off a upgrade request for each item in the upgrade "queue"
|
|---|
| 301 | */
|
|---|
| 302 | static function perform_queued_updates() {
|
|---|
| 303 |
|
|---|
| 304 | $lock_name = 'auto_upgrader.lock';
|
|---|
| 305 | if ( get_site_transient( $lock_name ) ) {
|
|---|
| 306 | // Test to see if it was set more than an hour ago, if so, cleanup.
|
|---|
| 307 | if ( true || get_site_transient( $lock_name ) < ( time() - HOUR_IN_SECONDS ) )
|
|---|
| 308 | delete_site_transient( $lock_name );
|
|---|
| 309 | else // Recent lock
|
|---|
| 310 | return;
|
|---|
| 311 | }
|
|---|
| 312 | // Lock upgrades for us for half an hour
|
|---|
| 313 | if ( ! set_site_transient( $lock_name, microtime( true ), HOUR_IN_SECONDS / 2 ) )
|
|---|
| 314 | return;
|
|---|
| 315 |
|
|---|
| 316 | // Admin-ize ourselves
|
|---|
| 317 | self::load_required_admin_includes();
|
|---|
| 318 |
|
|---|
| 319 | // Next, Plugins
|
|---|
| 320 | wp_update_plugins(); // Check for Plugin updates
|
|---|
| 321 | $plugin_updates = get_site_transient( 'update_plugins' );
|
|---|
| 322 | if ( $plugin_updates && !empty( $plugin_updates->response ) ) {
|
|---|
| 323 | foreach ( array_keys( $plugin_updates->response ) as $plugin ) {
|
|---|
| 324 | self::upgrade( 'plugin', $plugin );
|
|---|
| 325 | }
|
|---|
| 326 | // Force refresh of plugin update information
|
|---|
| 327 | wp_clean_plugins_cache();
|
|---|
| 328 | }
|
|---|
| 329 |
|
|---|
| 330 | // Next, those themes we all love
|
|---|
| 331 | wp_update_themes(); // Check for Theme updates
|
|---|
| 332 | $theme_updates = get_site_transient( 'update_themes' );
|
|---|
| 333 | if ( $theme_updates && !empty( $theme_updates->response ) ) {
|
|---|
| 334 | foreach ( array_keys( $theme_updates->response ) as $theme ) {
|
|---|
| 335 | self::upgrade( 'theme', $theme );
|
|---|
| 336 | }
|
|---|
| 337 | // Force refresh of theme update information
|
|---|
| 338 | wp_clean_themes_cache();
|
|---|
| 339 | }
|
|---|
| 340 |
|
|---|
| 341 | // Up first, Core.
|
|---|
| 342 | wp_version_check(); // Check for Core updates
|
|---|
| 343 | $core_update = self::find_core_auto_update();
|
|---|
| 344 | if ( $core_update )
|
|---|
| 345 | self::upgrade( 'core', $core_update );
|
|---|
| 346 |
|
|---|
| 347 | // Cleanup, These won't trigger any updates this time due to the locking transient
|
|---|
| 348 | wp_version_check(); // check for Core updates
|
|---|
| 349 | wp_update_themes(); // Check for Theme updates
|
|---|
| 350 | wp_update_plugins(); // Check for Plugin updates
|
|---|
| 351 |
|
|---|
| 352 | delete_site_transient( $lock_name );
|
|---|
| 353 |
|
|---|
| 354 | }
|
|---|
| 355 |
|
|---|
| 356 | /**
|
|---|
| 357 | * Make a loopback HTTP request to admin-ajax.php and the blogs front-end to ensure
|
|---|
| 358 | * that the blog isn't fataling, or encountering issues after the update
|
|---|
| 359 | */
|
|---|
| 360 | static function test_if_site_is_ok() {
|
|---|
| 361 | // Is the Admin ajax handler returning what we expect of it?
|
|---|
| 362 | $http_result = wp_remote_post( admin_url( 'admin-ajax.php' ), array( 'timeout' => 10 ) );
|
|---|
| 363 | $admin_ok = ! is_wp_error( $http_result ) && ( '0' === wp_remote_retrieve_body( $http_result ) );
|
|---|
| 364 |
|
|---|
| 365 | if ( ! $admin_ok )
|
|---|
| 366 | return false;
|
|---|
| 367 |
|
|---|
| 368 | // Is the front-end returning what we'd expect of it? (Note, this is a filter on template_redirect + wp_footer below)
|
|---|
| 369 | // This WILL break with caching plugins, a POST should make it past most caching plugins, but I suspect not all of them
|
|---|
| 370 | $http_result = wp_remote_post( site_url( '?test-upgrade-OK=1' ), array( 'timeout' => 10 ) );
|
|---|
| 371 | $front_ok = ! is_wp_error( $http_result ) && ( 'OK' === wp_remote_retrieve_body( $http_result ) );
|
|---|
| 372 |
|
|---|
| 373 | if ( ! $front_ok )
|
|---|
| 374 | return false;
|
|---|
| 375 |
|
|---|
| 376 | return true;
|
|---|
| 377 | }
|
|---|
| 378 |
|
|---|
| 379 | static function test_if_site_is_ok_callback() {
|
|---|
| 380 | if ( ! isset( $_GET['test-upgrade-OK'] ) )
|
|---|
| 381 | return;
|
|---|
| 382 |
|
|---|
| 383 | ob_start();
|
|---|
| 384 | add_action( 'wp_footer', function() {
|
|---|
| 385 | ob_end_clean();
|
|---|
| 386 | die( 'OK' );
|
|---|
| 387 | }, 999 );
|
|---|
| 388 | }
|
|---|
| 389 | }
|
|---|
| 390 | WP_Background_Upgrader::init();
|
|---|