WordPress.org

Make WordPress Core

Ticket #22704: background-upgrader.php

File background-upgrader.php, 13.4 KB (added by dd32, 4 years ago)
Line 
1<?php
2
3class 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}
390WP_Background_Upgrader::init();